diff --git a/appinfo/routes.php b/appinfo/routes.php index aedd91313caeaa606ed4b823858eca51c1d482e6..5eab965ea3d126f9d4dd68f149b5556a990a0ecc 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -23,5 +23,12 @@ return ['routes' => [ [ 'name' => 'beta_user#submit_issue', 'url' => '/issue/submit', 'verb' => 'POST' - ] + ], + + ['name' => 'account#index', 'url' => '/accounts/{lang}/signup', 'verb' => 'GET'], + ['name' => 'account#create', 'url' => '/accounts/create', 'verb' => 'POST'], + ['name' => 'account#captcha', 'url' => '/accounts/captcha', 'verb' => 'GET'], + ['name' => 'account#verify_captcha', 'url' => '/accounts/verify_captcha', 'verb' => 'POST'], + ['name' => 'account#check_username_available', 'url' => '/accounts/check_username_available', 'verb' => 'POST'], + ]]; diff --git a/img/success.svg b/img/success.svg new file mode 100644 index 0000000000000000000000000000000000000000..d3c0bed9a2fb97bf6957181f6e6fd039b998a6a5 --- /dev/null +++ b/img/success.svg @@ -0,0 +1,4 @@ + + + + diff --git a/l10n/de.js b/l10n/de.js index 47243d093f3c4b81b18aa2ca6be4fa179bb907f2..a205f974de6b75a4b391f1af76ae9c6aa7307c53 100644 --- a/l10n/de.js +++ b/l10n/de.js @@ -39,7 +39,40 @@ OC.L10N.register( "A subscription is active in this account. Please cancel it or let it expire before deleting your account.": "Für dieses Konto ist ein Abonnement aktiv. Bitte kündigen Sie es oder lassen Sie es auslaufen, bevor Sie Ihr Konto löschen.", "Loading...": "Laden...", "Temporary error contacting murena.com; please try again later!": "Vorübergehender Fehler bei der Kontaktaufnahme mit murena.com; bitte versuchen Sie es später noch einmal!", - "Murena Cloud 2FA": "Murena Cloud 2FA" - + "Create Murena Account": "Murena-Konto erstellen", + "Captcha Verification": "Captcha-Prüfung", + "For security reasons you need to set a recovery address for your Murena Cloud account.": "Aus Sicherheitsgründen müssen Sie eine Wiederherstellungsadresse für Ihr Murena Cloud-Konto festlegen.", + "As long as you don't, you'll have limited access to your account.": "Solange Sie das nicht tun, haben Sie nur eingeschränkten Zugang zu Ihrem Konto.", + "Create My Account": "Mein Konto erstellen", + "Verify": "Überprüfen Sie", + "Later": "Später", + "Set my recovery email address": "Meine Wiederherstellungs-E-Mail-Adresse festlegen", + "Display name": "Name anzeigen", + "Username": "Benutzername", + "Enter Password": "Passwort eingeben", + "I want to receive news about Murena products and promotions": "Ich möchte Neuigkeiten über Murena-Produkte und Werbeaktionen erhalten", + "I want to receive news about /e/OS": "Ich möchte Nachrichten über /e/OS erhalten.", + "Your name as shown to others": "Ihr Name, wie er für andere sichtbar ist", + "Password": "Passwort", + "Confirm": "Bestätigen Sie", + "Human Verification": "Menschliche Verifizierung", + "Recovery Email": "Wiederherstellung E-Mail", + "Display name is required.": "Anzeigename ist erforderlich.", + "Username is required.": "Der Benutzername ist erforderlich.", + "Username must consist of letters, numbers, hyphens, and underscores only.": "Der Benutzername darf nur aus Buchstaben, Zahlen, Bindestrichen und Unterstrichen bestehen.", + "Username must be at least 3 characters long.": "Der Benutzername muss mindestens 3 Zeichen lang sein.", + "Username is already taken.": "Benutzername ist bereits vergeben.", + "Password is required.": "Passwort ist erforderlich.", + "Confirm password is required.": "Passwortbestätigung ist erforderlich.", + "The confirm password does not match the password.": "Das Bestätigungskennwort stimmt nicht mit dem Kennwort überein.", + "Human Verification is required.": "Eine menschliche Verifizierung ist erforderlich.", + "Human Verification code is not correct.": "Der Human Verification Code ist nicht korrekt.", + "Recovery Email is required.": "Eine Wiederherstellungs-E-Mail ist erforderlich.", + "You must read and accept the Terms of Service to create your account.": "Sie müssen die Allgemeinen Geschäftsbedingungen lesen und akzeptieren, um Ihr Konto zu erstellen.", + "Use My Account Now": "Mein Konto jetzt verwenden", + "I have read and accept the Terms of Service.": "Ich habe die Nutzungsbedingungen gelesen und akzeptiere diese.", + "Success!": "Erfolgreich!", + "Your __username__@__domain__ account was successfully created.": "Ihr __username__@__domain__-Konto wurde erfolgreich erstellt.", + "If you want to use your murena.io email in a mail app like Thunderbird, Outlook or another, please visit this page.": "Wenn Sie Ihre murena.io-E-Mail in einer Mail-Anwendung wie Thunderbird, Outlook oder einer anderen nutzen möchten, besuchen Sie bitte diese Seite." }, "nplurals=2; plural=(n != 1);"); diff --git a/l10n/de.json b/l10n/de.json index d70e6d9c48b8c7b5eae7f582a3f34c565ba4caf8..54c2e88853eb257e06a34536078a21c95315d46d 100644 --- a/l10n/de.json +++ b/l10n/de.json @@ -38,7 +38,40 @@ "A subscription is active in this account. Please cancel it or let it expire before deleting your account.": "Für dieses Konto ist ein Abonnement aktiv. Bitte kündigen Sie es oder lassen Sie es auslaufen, bevor Sie Ihr Konto löschen.", "Loading...": "Laden...", "Temporary error contacting murena.com; please try again later!": "Vorübergehender Fehler bei der Kontaktaufnahme mit murena.com; bitte versuchen Sie es später noch einmal!", - "Murena Cloud 2FA": "Murena Cloud 2FA" + "Create Murena Account": "Murena-Konto erstellen", + "Captcha Verification": "Captcha-Prüfung", + "For security reasons you need to set a recovery address for your Murena Cloud account.": "Aus Sicherheitsgründen müssen Sie eine Wiederherstellungsadresse für Ihr Murena Cloud-Konto festlegen.", + "As long as you don't, you'll have limited access to your account.": "Solange Sie das nicht tun, haben Sie nur eingeschränkten Zugang zu Ihrem Konto.", + "Create My Account": "Mein Konto erstellen", + "Verify": "Überprüfen Sie", + "Later": "Später", + "Set my recovery email address": "Meine Wiederherstellungs-E-Mail-Adresse festlegen", + "Display name": "Name anzeigen", + "Username": "Benutzername", + "Enter Password": "Passwort eingeben", + "I want to receive news about Murena products and promotions": "Ich möchte Neuigkeiten über Murena-Produkte und Werbeaktionen erhalten", + "I want to receive news about /e/OS": "Ich möchte Nachrichten über /e/OS erhalten.", + "Your name as shown to others": "Ihr Name, wie er für andere sichtbar ist", + "Password": "Passwort", + "Confirm": "Bestätigen Sie", + "Human Verification": "Menschliche Verifizierung", + "Recovery Email": "Wiederherstellung E-Mail", + "Display name is required.": "Anzeigename ist erforderlich.", + "Username is required.": "Der Benutzername ist erforderlich.", + "Username must consist of letters, numbers, hyphens, and underscores only.": "Der Benutzername darf nur aus Buchstaben, Zahlen, Bindestrichen und Unterstrichen bestehen.", + "Username must be at least 3 characters long.": "Der Benutzername muss mindestens 3 Zeichen lang sein.", + "Username is already taken.": "Benutzername ist bereits vergeben.", + "Password is required.": "Passwort ist erforderlich.", + "Confirm password is required.": "Passwortbestätigung ist erforderlich.", + "The confirm password does not match the password.": "Das Bestätigungskennwort stimmt nicht mit dem Kennwort überein.", + "Human Verification is required.": "Eine menschliche Verifizierung ist erforderlich.", + "Human Verification code is not correct.": "Der Human Verification Code ist nicht korrekt.", + "Recovery Email is required.": "Eine Wiederherstellungs-E-Mail ist erforderlich.", + "You must read and accept the Terms of Service to create your account.": "Sie müssen die Allgemeinen Geschäftsbedingungen lesen und akzeptieren, um Ihr Konto zu erstellen.", + "I have read and accept the Terms of Service.": "Ich habe die Nutzungsbedingungen gelesen und akzeptiere diese.", + "Success!": "Erfolgreich!", + "Your __username__@__domain__ account was successfully created.": "Ihr __username__@__domain__-Konto wurde erfolgreich erstellt.", + "If you want to use your murena.io email in a mail app like Thunderbird, Outlook or another, please visit this page.": "Wenn Sie Ihre murena.io-E-Mail in einer Mail-Anwendung wie Thunderbird, Outlook oder einer anderen nutzen möchten, besuchen Sie bitte diese Seite." }, "pluralForm": "nplurals=2; plural=(n != 1);" } diff --git a/l10n/en.js b/l10n/en.js index 422054cf050c82973efbe9ff477421bcf45e3e78..3a55d272e30aa998621a017494d6eed82a7b83b7 100644 --- a/l10n/en.js +++ b/l10n/en.js @@ -41,6 +41,43 @@ OC.L10N.register( "A subscription is active in this account. Please cancel it or let it expire before deleting your account.": "A subscription is active in this account. Please cancel it or let it expire before deleting your account.", "Loading...": "Loading...", "Temporary error contacting murena.com; please try again later!": "Temporary error contacting murena.com; please try again later!", - "Murena Cloud 2FA": "Murena Cloud 2FA" + "Murena Cloud 2FA": "Murena Cloud 2FA", + "Create Murena Account": "Create Murena Account", + "Captcha Verification": "Captcha Verification", + "For security reasons you need to set a recovery address for your Murena Cloud account.": "For security reasons you need to set a recovery address for your Murena Cloud account.", + "As long as you don't, you'll have limited access to your account.": "As long as you don't, you'll have limited access to your account.", + "Create My Account": "Create My Account", + "Verify": "Verify", + "Later": "Later", + "Set my recovery email address": "Set my recovery email address", + "Display name": "Display name", + "Username": "Username", + "Enter Password": "Enter Password", + "I want to receive news about Murena products and promotions": "I want to receive news about Murena products and promotions", + "I want to receive news about /e/OS": "I want to receive news about /e/OS", + "Your name as shown to others": "Your name as shown to others", + "Password": "Password", + "Confirm": "Confirm", + "Human Verification": "Human Verification", + "Recovery Email": "Recovery Email", + "Display name is required.": "Display name is required.", + "Username is required.": "Username is required.", + "Username must consist of letters, numbers, hyphens, and underscores only.": "Username must consist of letters, numbers, hyphens, and underscores only.", + "Username must be at least 3 characters long.": "Username must be at least 3 characters long.", + "Username is already taken.": "Username is already taken.", + "Password is required.": "Password is required.", + "Confirm password is required.": "Confirm password is required.", + "The confirm password does not match the password.": "The confirm password does not match the password.", + "Human Verification is required.": "Human Verification is required.", + "Human Verification code is not correct.": "Human Verification code is not correct.", + "Recovery Email is required.": "Recovery Email is required.", + "You must read and accept the Terms of Service to create your account.": "You must read and accept the Terms of Service to create your account.", + "Use My Account Now": "Use My Account Now", + "I have read and accept the Terms of Service.": "I have read and accept the Terms of Service.", + "Success!": "Success!", + "Your __username__@__domain__ account was successfully created.": "Your __username__@__domain__ account was successfully created.", + "If you want to use your murena.io email in a mail app like Thunderbird, Outlook or another, please visit this page.": "If you want to use your murena.io email in a mail app like Thunderbird, Outlook or another, please visit this page.", + + "Recovery email address is already taken.": "Recovery email address is already taken." }, "nplurals=2; plural=(n != 1);"); diff --git a/l10n/en.json b/l10n/en.json index 83db6b42ebd09872e59fa0184ad360b3886fa76d..5d15841a073b134fa513550f84bb7c0574984e43 100644 --- a/l10n/en.json +++ b/l10n/en.json @@ -38,7 +38,42 @@ "A subscription is active in this account. Please cancel it or let it expire before deleting your account.": "A subscription is active in this account. Please cancel it or let it expire before deleting your account.", "Loading...": "Loading...", "Temporary error contacting murena.com; please try again later!": "Temporary error contacting murena.com; please try again later!", - "Murena Cloud 2FA": "Murena Cloud 2FA" + "Murena Cloud 2FA": "Murena Cloud 2FA", + "Create Murena Account": "Create Murena Account", + "Captcha Verification": "Captcha Verification", + "For security reasons you need to set a recovery address for your Murena Cloud account.": "For security reasons you need to set a recovery address for your Murena Cloud account.", + "As long as you don't, you'll have limited access to your account.": "As long as you don't, you'll have limited access to your account.", + "Create My Account": "Create My Account", + "Verify": "Verify", + "Later": "Later", + "Set my recovery email address": "Set my recovery email address", + "Display name": "Display name", + "Username": "Username", + "Enter Password": "Enter Password", + "I want to receive news about Murena products and promotions": "I want to receive news about Murena products and promotions", + "I want to receive news about /e/OS": "I want to receive news about /e/OS", + "Your name as shown to others": "Your name as shown to others", + "Password": "Password", + "Confirm": "Confirm", + "Human Verification": "Human Verification", + "Recovery Email": "Recovery Email", + "Display name is required.": "Display name is required.", + "Username is required.": "Username is required.", + "Username must consist of letters, numbers, hyphens, and underscores only.": "Username must consist of letters, numbers, hyphens, and underscores only.", + "Username must be at least 3 characters long.": "Username must be at least 3 characters long.", + "Username is already taken.": "Username is already taken.", + "Password is required.": "Password is required.", + "Confirm password is required.": "Confirm password is required.", + "The confirm password does not match the password.": "The confirm password does not match the password.", + "Human Verification is required.": "Human Verification is required.", + "Human Verification code is not correct.": "Human Verification code is not correct.", + "Recovery Email is required.": "Recovery Email is required.", + "You must read and accept the Terms of Service to create your account.": "You must read and accept the Terms of Service to create your account.", + "Use My Account Now": "Use My Account Now", + "I have read and accept the Terms of Service.": "I have read and accept the Terms of Service.", + "Success!": "Success!", + "Your __username__@__domain__ account was successfully created.": "Your __username__@__domain__ account was successfully created.", + "If you want to use your murena.io email in a mail app like Thunderbird, Outlook or another, please visit this page.": "If you want to use your murena.io email in a mail app like Thunderbird, Outlook or another, please visit this page." }, "pluralForm": "nplurals=2; plural=(n != 1);" } diff --git a/l10n/es.js b/l10n/es.js index 3a081dd1f065b26659ffbe20f81e80cd92e5dc03..ea832b365aed404846ddb56b04551c75a9342f21 100644 --- a/l10n/es.js +++ b/l10n/es.js @@ -40,6 +40,41 @@ OC.L10N.register( "A subscription is active in this account. Please cancel it or let it expire before deleting your account.": "Hay una suscripción activa en esta cuenta. Por favor, cancélala o deja que expire antes de eliminar tu cuenta.", "Loading...": "Cargando...", "Temporary error contacting murena.com; please try again later!": "Error temporal al contactar con murena.com; ¡por favor, inténtalo más tarde!", - "Murena Cloud 2FA": "Nube Murena A2F" + "Murena Cloud 2FA": "Nube Murena A2F", + "Create Murena Account": "Crear cuenta Murena", + "Captcha Verification": "Verificación Captcha", + "For security reasons you need to set a recovery address for your Murena Cloud account.": "Por razones de seguridad, debe establecer una dirección de recuperación para su cuenta de Murena Cloud.", + "As long as you don't, you'll have limited access to your account.": "Mientras no lo hagas, tendrás acceso limitado a tu cuenta.", + "Create My Account": "Crear mi cuenta", + "Verify": "Verifique", + "Later": "Más tarde", + "Set my recovery email address": "Establecer mi dirección de correo electrónico de recuperación", + "Display name": "Mostrar nombre", + "Username": "Nombre de usuario", + "Enter Password": "Introducir contraseña", + "I want to receive news about Murena products and promotions": "Deseo recibir noticias sobre productos y promociones de Murena", + "I want to receive news about /e/OS": "Quiero recibir noticias sobre /e/OS", + "Your name as shown to others": "Su nombre ante los demás", + "Password": "Contraseña", + "Confirm": "Confirme", + "Human Verification": "Verificación humana", + "Recovery Email": "Correo electrónico de recuperación", + "Display name is required.": "El nombre para mostrar es obligatorio.", + "Username is required.": "Nombre de usuario obligatorio.", + "Username must consist of letters, numbers, hyphens, and underscores only.": "El nombre de usuario debe estar compuesto únicamente por letras, números, guiones y guiones bajos.", + "Username must be at least 3 characters long.": "El nombre de usuario debe tener al menos 3 caracteres.", + "Username is already taken.": "El nombre de usuario ya está ocupado.", + "Password is required.": "Se requiere contraseña.", + "Confirm password is required.": "Es necesario confirmar la contraseña.", + "The confirm password does not match the password.": "La contraseña confirmada no coincide con la contraseña.", + "Human Verification is required.": "Se requiere verificación humana.", + "Human Verification code is not correct.": "El código de verificación humana no es correcto.", + "Recovery Email is required.": "Se requiere correo electrónico de recuperación.", + "You must read and accept the Terms of Service to create your account.": "Debe leer y aceptar las Condiciones del servicio para crear su cuenta.", + "Use My Account Now": "Utilizar mi cuenta ahora", + "I have read and accept the Terms of Service.": "He leído y acepto las Condiciones del servicio.", + "Success!": "¡Éxito!", + "Your __username__@__domain__ account was successfully created.": "Su cuenta __username__@__domain__ se ha creado correctamente.", + "If you want to use your murena.io email in a mail app like Thunderbird, Outlook or another, please visit this page.": "Si desea utilizar su correo electrónico de murena.io en una aplicación de correo como Thunderbird, Outlook u otra, visite esta página.", }, "nplurals=2; plural=(n != 1);"); diff --git a/l10n/es.json b/l10n/es.json index b85948651e29e601a4e765f2a30317a0abeadf6b..e5c3daa6daf6bd5a6e22003c243a7e269eb07c2c 100644 --- a/l10n/es.json +++ b/l10n/es.json @@ -2,7 +2,7 @@ "translations": { "Email Address": "Correo electrónico", "Options": "Opciones", - "We are going to proceed with your cloud account suppression.": "Vamos a proceder a la supresión de su cuenta en la nube. ", + "We are going to proceed with your cloud account suppression.": "Vamos a proceder a la supresión de su cuenta en la nube.", "Check the box below if you also want to delete the associated shop account(s).": "Marque la casilla siguiente si también desea eliminar la(s) cuenta(s) de la tienda asociada(s).", "For your information you have %d order(s) in your account.": "Para su información tiene %d orden(es) en su cuenta.", "For your information you have %d order(s) in your accounts: ": "Para su información tiene %d orden(es) en sus cuentas: ", @@ -18,6 +18,7 @@ "Do you want to become a beta user?": "Desea convertirse en usuario beta?", "You want to experiment new features ahead of the others and provide feedback on them before and if they're released? This section is made for you!": "Quieres experimentar las nuevas funciones antes que los demás y dar tu opinión sobre ellas antes y si se publican? Esta sección está hecha para ti.", "To get a preview of our new features you need to become part of our beta users. To do so, simply click on the button below. You can opt out of beta features at anytime.": "Para obtener una vista previa que muestre nuestras nuevas funciones, debe formar parte de nuestros usuarios beta. Para ello, simplemente haga clic en el botón de abajo. Puedes excluirte de las funciones beta en cualquier momento.", + "I agree with terms & conditions.": "Estoy de acuerdo con los términos y condiciones.", "Opt out of beta features": "No participar en las funciones beta", "Become a beta user": "Convertirse en usuario beta", "Are you sure you want to opt out of beta features?": "Estás seguro de que quieres excluir las funciones beta?", @@ -38,7 +39,42 @@ "A subscription is active in this account. Please cancel it or let it expire before deleting your account.": "Hay una suscripción activa en esta cuenta. Por favor, cancélala o deja que expire antes de eliminar tu cuenta.", "Loading...": "Cargando...", "Temporary error contacting murena.com; please try again later!": "Error temporal al contactar con murena.com; ¡por favor, inténtalo más tarde!", - "Murena Cloud 2FA": "Nube Murena A2F" + "Murena Cloud 2FA": "Nube Murena A2F", + "Create Murena Account": "Crear cuenta Murena", + "Captcha Verification": "Verificación Captcha", + "For security reasons you need to set a recovery address for your Murena Cloud account.": "Por razones de seguridad, debe establecer una dirección de recuperación para su cuenta de Murena Cloud.", + "As long as you don't, you'll have limited access to your account.": "Mientras no lo hagas, tendrás acceso limitado a tu cuenta.", + "Create My Account": "Crear mi cuenta", + "Verify": "Verifique", + "Later": "Más tarde", + "Set my recovery email address": "Establecer mi dirección de correo electrónico de recuperación", + "Display name": "Mostrar nombre", + "Username": "Nombre de usuario", + "Enter Password": "Introducir contraseña", + "I want to receive news about Murena products and promotions": "Deseo recibir noticias sobre productos y promociones de Murena", + "I want to receive news about /e/OS": "Quiero recibir noticias sobre /e/OS", + "Your name as shown to others": "Su nombre ante los demás", + "Password": "Contraseña", + "Confirm": "Confirme", + "Human Verification": "Verificación humana", + "Recovery Email": "Correo electrónico de recuperación", + "Display name is required.": "El nombre para mostrar es obligatorio.", + "Username is required.": "Nombre de usuario obligatorio.", + "Username must consist of letters, numbers, hyphens, and underscores only.": "El nombre de usuario debe estar compuesto únicamente por letras, números, guiones y guiones bajos.", + "Username must be at least 3 characters long.": "El nombre de usuario debe tener al menos 3 caracteres.", + "Username is already taken.": "El nombre de usuario ya está ocupado.", + "Password is required.": "Se requiere contraseña.", + "Confirm password is required.": "Es necesario confirmar la contraseña.", + "The confirm password does not match the password.": "La contraseña confirmada no coincide con la contraseña.", + "Human Verification is required.": "Se requiere verificación humana.", + "Human Verification code is not correct.": "El código de verificación humana no es correcto.", + "Recovery Email is required.": "Se requiere correo electrónico de recuperación.", + "You must read and accept the Terms of Service to create your account.": "Debe leer y aceptar las Condiciones del servicio para crear su cuenta.", + "Use My Account Now": "Utilizar mi cuenta ahora", + "I have read and accept the Terms of Service.": "He leído y acepto las Condiciones del servicio.", + "Success!": "¡Éxito!", + "Your __username__@__domain__ account was successfully created.": "Su cuenta __username__@__domain__ se ha creado correctamente.", + "If you want to use your murena.io email in a mail app like Thunderbird, Outlook or another, please visit this page.": "Si desea utilizar su correo electrónico de murena.io en una aplicación de correo como Thunderbird, Outlook u otra, visite esta página." }, "pluralForm": "nplurals=2; plural=(n != 1);" } diff --git a/l10n/fr.js b/l10n/fr.js index 1f668c1fe41989b17f237785be77526930d9fba4..81fb0b815e31104adc940f6eb9b35f5a35f49b49 100644 --- a/l10n/fr.js +++ b/l10n/fr.js @@ -39,6 +39,41 @@ OC.L10N.register( "A subscription is active in this account. Please cancel it or let it expire before deleting your account.": "Un abonnement est actif dans ce compte. Veuillez l'annuler ou le laisser expirer avant de supprimer votre compte.", "Loading...": "Chargement...", "Temporary error contacting murena.com; please try again later!": "Erreur temporaire en contactant murena.com ; veuillez réessayer plus tard !", - "Murena Cloud 2FA": "Authentification à 2 facteurs Murena Cloud" + "Murena Cloud 2FA": "Authentification à 2 facteurs Murena Cloud", + "Create Murena Account": "Créer un compte Murena", + "Captcha Verification": "Vérification Captcha", + "For security reasons you need to set a recovery address for your Murena Cloud account.": "Pour des raisons de sécurité, vous devez définir une adresse de récupération pour votre compte Murena Cloud.", + "As long as you don't, you'll have limited access to your account.": "Si vous ne le faites pas, vous aurez un accès limité à votre compte.", + "Create My Account": "Créer mon compte", + "Verify": "Vérifier", + "Later": "Plus tard", + "Set my recovery email address": "Définir mon adresse e-mail de récupération", + "Display name": "Nom d'affichage", + "Username": "Nom d'utilisateur", + "Enter Password": "Saisir le mot de passe", + "I want to receive news about Murena products and promotions": "Je souhaite recevoir des informations sur les produits et les promotions de Murena.", + "I want to receive news about /e/OS": "Je souhaite recevoir des informations sur /e/OS.", + "Your name as shown to others": "Votre nom tel qu'il est présenté aux autres", + "Password": "Mot de passe", + "Confirm": "Confirmer", + "Human Verification": "Vérification humaine", + "Recovery Email": "Récupération de l'email", + "Display name is required.": "Le nom d'affichage est obligatoire.", + "Username is required.": "Le nom d'utilisateur est requis.", + "Username must consist of letters, numbers, hyphens, and underscores only.": "Le nom d'utilisateur doit être composé uniquement de lettres, de chiffres, de traits d'union et de traits de soulignement.", + "Username must be at least 3 characters long.": "Le nom d'utilisateur doit comporter au moins 3 caractères.", + "Username is already taken.": "Le nom d'utilisateur est déjà pris.", + "Password is required.": "Le mot de passe est requis.", + "Confirm password is required.": "La confirmation du mot de passe est requise.", + "The confirm password does not match the password.": "Le mot de passe de confirmation ne correspond pas au mot de passe.", + "Human Verification is required.": "Une vérification humaine est nécessaire.", + "Human Verification code is not correct.": "Le code de vérification humaine n'est pas correct.", + "Recovery Email is required.": "L'e-mail de récupération est requis.", + "You must read and accept the Terms of Service to create your account.": "Vous devez lire et accepter les conditions de service pour créer votre compte.", + "Use My Account Now": "Utiliser mon compte maintenant", + "I have read and accept the Terms of Service.": "J'ai lu et j'accepte les Termes de service.", + "Success!": "Succès !", + "Your __username__@__domain__ account was successfully created.": "Votre compte __username__@__domain__ a été créé avec succès.", + "If you want to use your murena.io email in a mail app like Thunderbird, Outlook or another, please visit this page.": "Si vous souhaitez utiliser votre email murena.io dans une application de messagerie comme Thunderbird, Outlook ou autre, veuillez visiter cette page." }, "nplurals=2; plural=(n != 1);"); diff --git a/l10n/fr.json b/l10n/fr.json index 48a79c476f72f15dcc0fd72f89a12e6e26092bbf..a371d46de95a16641ee43c65b4ee767cf5036f5f 100644 --- a/l10n/fr.json +++ b/l10n/fr.json @@ -38,7 +38,42 @@ "A subscription is active in this account. Please cancel it or let it expire before deleting your account.": "Un abonnement est actif dans ce compte. Veuillez l'annuler ou le laisser expirer avant de supprimer votre compte.", "Loading...": "Chargement...", "Temporary error contacting murena.com; please try again later!": "Erreur temporaire en contactant murena.com ; veuillez réessayer plus tard !", - "Murena Cloud 2FA": "Authentification à 2 facteurs Murena Cloud" + "Murena Cloud 2FA": "Authentification à 2 facteurs Murena Cloud", + "Create Murena Account": "Créer un compte Murena", + "Captcha Verification": "Vérification Captcha", + "For security reasons you need to set a recovery address for your Murena Cloud account.": "Pour des raisons de sécurité, vous devez définir une adresse de récupération pour votre compte Murena Cloud.", + "As long as you don't, you'll have limited access to your account.": "Si vous ne le faites pas, vous aurez un accès limité à votre compte.", + "Create My Account": "Créer mon compte", + "Verify": "Vérifier", + "Later": "Plus tard", + "Set my recovery email address": "Définir mon adresse e-mail de récupération", + "Display name": "Nom d'affichage", + "Username": "Nom d'utilisateur", + "Enter Password": "Saisir le mot de passe", + "I want to receive news about Murena products and promotions": "Je souhaite recevoir des informations sur les produits et les promotions de Murena.", + "I want to receive news about /e/OS": "Je souhaite recevoir des informations sur /e/OS.", + "Your name as shown to others": "Votre nom tel qu'il est présenté aux autres", + "Password": "Mot de passe", + "Confirm": "Confirmer", + "Human Verification": "Vérification humaine", + "Recovery Email": "Récupération de l'email", + "Display name is required.": "Le nom d'affichage est obligatoire.", + "Username is required.": "Le nom d'utilisateur est requis.", + "Username must consist of letters, numbers, hyphens, and underscores only.": "Le nom d'utilisateur doit être composé uniquement de lettres, de chiffres, de traits d'union et de traits de soulignement.", + "Username must be at least 3 characters long.": "Le nom d'utilisateur doit comporter au moins 3 caractères.", + "Username is already taken.": "Le nom d'utilisateur est déjà pris.", + "Password is required.": "Le mot de passe est requis.", + "Confirm password is required.": "La confirmation du mot de passe est requise.", + "The confirm password does not match the password.": "Le mot de passe de confirmation ne correspond pas au mot de passe.", + "Human Verification is required.": "Une vérification humaine est nécessaire.", + "Human Verification code is not correct.": "Le code de vérification humaine n'est pas correct.", + "Recovery Email is required.": "L'e-mail de récupération est requis.", + "You must read and accept the Terms of Service to create your account.": "Vous devez lire et accepter les conditions de service pour créer votre compte.", + "Use My Account Now": "Utiliser mon compte maintenant", + "I have read and accept the Terms of Service.": "J'ai lu et j'accepte les Termes de service.", + "Success!": "Succès !", + "Your __username__@__domain__ account was successfully created.": "Votre compte __username__@__domain__ a été créé avec succès.", + "If you want to use your murena.io email in a mail app like Thunderbird, Outlook or another, please visit this page.": "Si vous souhaitez utiliser votre email murena.io dans une application de messagerie comme Thunderbird, Outlook ou autre, veuillez visiter cette page." }, "pluralForm": "nplurals=2; plural=(n != 1);" } diff --git a/l10n/it.js b/l10n/it.js index 7dbe44342f44b4b438a69967d6809f1fbc043f44..008a8d01c31d18ecb1250f8c747a57dda5159aec 100644 --- a/l10n/it.js +++ b/l10n/it.js @@ -39,6 +39,41 @@ OC.L10N.register( "A subscription is active in this account. Please cancel it or let it expire before deleting your account.": "In questo account è attivo un abbonamento. Si prega di annullarlo o di lasciarlo scadere prima di cancellare l'account.", "Loading...": "Caricamento...", "Temporary error contacting murena.com; please try again later!": "Errore temporaneo nel contattare murena.com; riprova più tardi!", - "Murena Cloud 2FA": "Codice di Autenticazione a 2 Fattori Murena Cloud" + "Murena Cloud 2FA": "Codice di Autenticazione a 2 Fattori Murena Cloud", + "Create Murena Account": "Creare un account Murena", + "Captcha Verification": "Verifica Captcha", + "For security reasons you need to set a recovery address for your Murena Cloud account.": "Per motivi di sicurezza è necessario impostare un indirizzo di recupero per il proprio account Murena Cloud.", + "As long as you don't, you'll have limited access to your account.": "Finché non lo farete, avrete un accesso limitato al vostro conto.", + "Create My Account": "Crea il mio account", + "Verify": "Verifica", + "Later": "Più tardi", + "Set my recovery email address": "Impostare l'indirizzo e-mail di recupero", + "Display name": "Nome visualizzato", + "Username": "Nome utente", + "Enter Password": "Inserire la password", + "I want to receive news about Murena products and promotions": "Desidero ricevere notizie sui prodotti e sulle promozioni Murena.", + "I want to receive news about /e/OS": "Voglio ricevere notizie su /e/OS.", + "Your name as shown to others": "Il vostro nome come mostrato agli altri", + "Password": "Password", + "Confirm": "Confermare", + "Human Verification": "Verifica umana", + "Recovery Email": "Recupero e-mail", + "Display name is required.": "Il nome visualizzato è obbligatorio.", + "Username is required.": "Il nome utente è necessario.", + "Username must consist of letters, numbers, hyphens, and underscores only.": "Il nome utente deve essere composto esclusivamente da lettere, numeri, trattini e trattini bassi.", + "Username must be at least 3 characters long.": "Il nome utente deve essere lungo almeno 3 caratteri.", + "Username is already taken.": "Il nome utente è già stato preso.", + "Password is required.": "La password è necessaria.", + "Confirm password is required.": "È necessario confermare la password.", + "The confirm password does not match the password.": "La password di conferma non corrisponde alla password.", + "Human Verification is required.": "È necessaria la verifica umana.", + "Human Verification code is not correct.": "Il codice di verifica umano non è corretto.", + "Recovery Email is required.": "L'e-mail di recupero è necessaria.", + "You must read and accept the Terms of Service to create your account.": "Per creare il proprio account è necessario leggere e accettare i Termini di servizio.", + "Use My Account Now": "Utilizza ora il mio account", + "I have read and accept the Terms of Service.": "Ho letto e accetto i Termini di servizio.", + "Success!": "Successo!", + "Your __username__@__domain__ account was successfully created.": "L'account __username__@__domain__ è stato creato con successo.", + "If you want to use your murena.io email in a mail app like Thunderbird, Outlook or another, please visit this page.": "Se si desidera utilizzare l'e-mail di murena.io in un'applicazione di posta elettronica come Thunderbird, Outlook o altre, visitare questa pagina." }, "nplurals=2; plural=(n != 1);"); diff --git a/l10n/it.json b/l10n/it.json index 1c9cb7a01cefe6e4e596ea3a9bfa638ac99af307..2dcaf6c59748bdfdf8ede6ab443109a6bbd821a6 100644 --- a/l10n/it.json +++ b/l10n/it.json @@ -2,7 +2,7 @@ "translations": { "Email Address": "Indirizzo e-mail", "Options": "Opzioni", - "We are going to proceed with your cloud account suppression.": "Procederemo con la soppressione dell'account cloud. ", + "We are going to proceed with your cloud account suppression.": "Procederemo con la soppressione dell'account cloud.", "Check the box below if you also want to delete the associated shop account(s).": "Selezionare la casella sottostante se si desidera eliminare anche gli account del negozio associati.", "For your information you have %d order(s) in your account.": "Per vostra informazione, avete %d ordini in il tuo account.", "For your information you have %d order(s) in your accounts: ": "Per vostra informazione, avete %d ordini in i vostri conti: ", @@ -38,7 +38,37 @@ "A subscription is active in this account. Please cancel it or let it expire before deleting your account.": "In questo account è attivo un abbonamento. Si prega di annullarlo o di lasciarlo scadere prima di cancellare l'account.", "Loading...": "Caricamento...", "Temporary error contacting murena.com; please try again later!": "Errore temporaneo nel contattare murena.com; riprova più tardi!", - "Murena Cloud 2FA": "Codice di Autenticazione a 2 Fattori Murena Cloud" + "Murena Cloud 2FA": "Codice di Autenticazione a 2 Fattori Murena Cloud", + "Create Murena Account": "Creare un account Murena", + "Captcha Verification": "Verifica Captcha", + "For security reasons you need to set a recovery address for your Murena Cloud account.": "Per motivi di sicurezza è necessario impostare un indirizzo di recupero per il proprio account Murena Cloud.", + "As long as you don't, you'll have limited access to your account.": "Finché non lo farete, avrete un accesso limitato al vostro conto.", + "Create My Account": "Crea il mio account", + "Verify": "Verifica", + "Later": "Più tardi", + "Set my recovery email address": "Impostare l'indirizzo e-mail di recupero", + "Display name": "Nome visualizzato", + "Username": "Nome utente", + "Enter Password": "Inserire la password", + "I want to receive news about Murena products and promotions": "Desidero ricevere notizie sui prodotti e sulle promozioni Murena.", + "I want to receive news about /e/OS": "Voglio ricevere notizie su /e/OS.", + "Your name as shown to others": "Il vostro nome come mostrato agli altri", + "Password": "Password", + "Confirm": "Confermare", + "Human Verification": "Verifica umana", + "Recovery Email": "Recupero e-mail", + "Display name is required.": "Il nome visualizzato è obbligatorio.", + "Username is required.": "Il nome utente è necessario.", + "Username must consist of letters, numbers, hyphens, and underscores only.": "Il nome utente deve essere composto esclusivamente da lettere, numeri, trattini e trattini bassi.", + "Username must be at least 3 characters long.": "Il nome utente deve essere lungo almeno 3 caratteri.", + "Username is already taken.": "Il nome utente è già stato preso.", + "Password is required.": "La password è necessaria.", + "Confirm password is required.": "È necessario confermare la password.", + "The confirm password does not match the password.": "La password di conferma non corrisponde alla password.", + "Human Verification is required.": "È necessaria la verifica umana.", + "Human Verification code is not correct.": "Il codice di verifica umano non è corretto.", + "Recovery Email is required.": "L'e-mail di recupero è necessaria.", + "You must read and accept the Terms of Service to create your account.": "Per creare il proprio account è necessario leggere e accettare i Termini di servizio." }, "pluralForm": "nplurals=2; plural=(n != 1);" } diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 6683fcf3f885d290931a8f7b5c6ac25041368531..890b535549bb043f6a7b7e1095bca05b76e23d05 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -26,7 +26,6 @@ declare(strict_types=1); namespace OCA\EcloudAccounts\AppInfo; -use OCA\EcloudAccounts\Listeners\BeforeTemplateRenderedListener; use OCA\EcloudAccounts\Listeners\BeforeUserDeletedListener; use OCA\EcloudAccounts\Listeners\TwoFactorStateChangedListener; use OCA\EcloudAccounts\Listeners\UserChangedListener; @@ -36,7 +35,6 @@ use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\Bootstrap\IRegistrationContext; -use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent; use OCP\IUserManager; use OCP\User\Events\BeforeUserDeletedEvent; use OCP\User\Events\UserChangedEvent; @@ -52,7 +50,6 @@ class Application extends App implements IBootstrap { $context->registerEventListener(BeforeUserDeletedEvent::class, BeforeUserDeletedListener::class); $context->registerEventListener(UserChangedEvent::class, UserChangedListener::class); $context->registerEventListener(StateChanged::class, TwoFactorStateChangedListener::class); - // $context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class); } public function boot(IBootContext $context): void { diff --git a/lib/Controller/AccountController.php b/lib/Controller/AccountController.php new file mode 100644 index 0000000000000000000000000000000000000000..505831be12a37106f0349771ad28d728261b3d09 --- /dev/null +++ b/lib/Controller/AccountController.php @@ -0,0 +1,213 @@ +appName = $AppName; + $this->userService = $userService; + $this->captchaService = $captchaService; + $this->l10nFactory = $l10nFactory; + $this->session = $session; + $this->userSession = $userSession; + $this->urlGenerator = $urlGenerator; + } + + /** + * @NoAdminRequired + * @PublicPage + * @NoCSRFRequired + * + * @param string $lang Language code (default: 'en') + * + */ + public function index(string $lang = 'en') { + if ($this->userSession->isLoggedIn()) { + return new RedirectResponse($this->urlGenerator->linkToDefaultPageUrl()); + } + return new TemplateResponse( + Application::APP_ID, + 'signup', + ['appName' => Application::APP_ID, 'lang' => $lang], + TemplateResponse::RENDER_AS_GUEST + ); + } + /** + * @NoAdminRequired + * @PublicPage + * @NoCSRFRequired + * + * @param string $displayname User's display name + * @param string $recoveryEmail User's recovery email + * @param string $username User's username + * @param string $password User's password + * @param string $language User's language preference + * + * @return \OCP\AppFramework\Http\DataResponse + */ + public function create(string $displayname = '', string $recoveryEmail = '', string $username = '', string $password = '', string $language = ''): DataResponse { + + $response = new DataResponse(); + + if(!$this->session->get('captcha_verified')) { + $response->setData(['message' => 'Captcha is not verified!', 'success' => false]); + $response->setStatus(400); + return $response; + } + + $inputData = [ + 'username' => ['value' => $username, 'maxLength' => 30], + 'displayname' => ['value' => $displayname, 'maxLength' => 30], + 'password' => ['value' => $password, 'maxLength' => 1024], + ]; + + foreach ($inputData as $inputName => $inputInfo) { + $validationError = $this->validateInput($inputName, $inputInfo['value'], $inputInfo['maxLength']); + if ($validationError !== null) { + $response->setData(['message' => $validationError, 'success' => false]); + $response->setStatus(400); + return $response; + } + } + + try { + $mainDomain = $this->userService->getMainDomain(); + $userEmail = $username.'@'.$mainDomain; + + $newUserEntry = $this->userService->registerUser($displayname, $recoveryEmail, $username, $userEmail, $password); + + $this->userService->setAccountDataLocally($username, $userEmail, $newUserEntry['quota']); + $this->userService->createHMEAlias($username, $userEmail); + $this->userService->createNewDomainAlias($username, $userEmail); + $this->userService->setTOS($username, true); + $this->userService->setUserLanguage($username, $language); + + if($recoveryEmail !== '') { + $this->userService->setRecoveryEmail($username, $recoveryEmail); + } + + $this->userService->sendWelcomeEmail($displayname, $username, $userEmail, $language); + + $response->setStatus(200); + $response->setData(['success' => true]); + + } catch (Exception $e) { + $response->setData(['message' => $e->getMessage(), 'success' => false]); + $response->setStatus(500); + } + return $response; + } + /** + * Validate input for a given input name, value, and optional maximum length. + * + * @param string $inputName The name of the input. + * @param string $value The value of the input. + * @param int|null $maxLength The optional maximum length allowed. + * + * @return string|null If validation fails, a string describing the error; otherwise, null. + */ + public function validateInput(string $inputName, string $value, int $maxLength = null) : ?string { + if ($value === '') { + return "$inputName is required."; + } + + if ($maxLength !== null && strlen($value) > $maxLength) { + return "$inputName is too large."; + } + + return null; // Validation passed + } + /** + * Check if a username is available. + * + * @NoAdminRequired + * @PublicPage + * @NoCSRFRequired + * + * @param string $username The username to check. + * + * @return \OCP\AppFramework\Http\DataResponse + */ + public function checkUsernameAvailable(string $username) : DataResponse { + $response = new DataResponse(); + $response->setStatus(400); + if (!$this->userService->userExists($username)) { + $response->setStatus(200); + } + return $response; + } + + /** + * @NoAdminRequired + * @PublicPage + * @NoCSRFRequired + */ + public function captcha(): Http\DataDisplayResponse { + $captchaValue = $this->captchaService->generateCaptcha(); + + $response = new Http\DataDisplayResponse($captchaValue, Http::STATUS_OK, ['Content-Type' => 'image/png']); + + return $response; + } + /** + * Verify a human verification input against captcha session values. + * + * @NoAdminRequired + * @PublicPage + * @NoCSRFRequired + * + * @param string $captchaInput The user-provided human verification input. + * + * @return \OCP\AppFramework\Http\DataResponse + */ + public function verifyCaptcha(string $captchaInput = '') : DataResponse { + $response = new DataResponse(); + + $captchaResult = (string)$this->session->get('captcha_result', ''); + $response->setStatus(400); + if ($captchaResult === $captchaInput) { + $this->session->remove('captcha_result'); + $this->session->set('captcha_verified', true); + $response->setStatus(200); + } + return $response; + } + +} diff --git a/lib/Service/CaptchaService.php b/lib/Service/CaptchaService.php new file mode 100644 index 0000000000000000000000000000000000000000..4bc4245dd51867f16705779c5643762304d51283 --- /dev/null +++ b/lib/Service/CaptchaService.php @@ -0,0 +1,228 @@ +session = $session; + } + /** + * Generate a captcha image and return its binary representation. + * + * @return string|null Binary representation of the generated image, or null on failure. + */ + public function generateCaptcha(): ?string { + // Configuration parameters + $width = self::WIDTH; + $height = self::HEIGHT; + $numbers = self::NUMBERS; + $symbols = self::SYMBOLS; + $noiseLevel = self::NOISE_LEVEL; + + // Create the initial image resource + $im = imagecreatetruecolor($width, $height); + $ns = imagecolorallocate($im, 200, 200, 200); // Noise color + $image = imagecreate($width, $height); + if (!$image) { + return null; + } + + // Draw random lines on the image + $this->drawRandomLines($image, 10); + + $x = 10 + mt_rand(0, 10); + $num1 = $this->getRandomCharacter($numbers); + $this->updateImage($image, $x, $num1); + + $x += 10 + mt_rand(0, 10); + $sym = $this->getRandomCharacter($symbols); + $this->updateImage($image, $x, $sym); + + $x += 10 + mt_rand(0, 10); + $num2 = $this->getRandomCharacter($numbers); + $this->updateImage($image, $x, $num2); + + // Rotate the image by a random angle + $image = imagerotate($image, mt_rand(-15, 15), 0); + + // Draw a random space and the equal sign + $x = $this->drawCharacterWithRandomSpace($image, $x, "="); + + // Combine the generated code + $code = $num1 . $sym . $num2; + + // Evaluate the mathematical expression + eval("\$code = $code;"); + + // Add random noise to the image + $this->addNoise($image, $noiseLevel); + + // Output the image as PNG into a variable + ob_start(); + imagepng($image); + $imageData = ob_get_clean(); + + // Destroy the image resource + imagedestroy($image); + + $num1 = intval($num1); + $num2 = intval($num2); + // Calculate result + $result = $this->calculateResult($num1, $num2, $sym); + + // Update session with the result + $this->updateSession($result); + + // Return the binary representation of the generated image + return $imageData; + } + /** + * Calculate the result of a mathematical operation. + * + * @param int $operand1 The first operand. + * @param int $operand2 The second operand. + * @param string $operator The mathematical operator ('+' or '-'). + * + * @return int The result of calcuulated + */ + private function calculateResult(int $operand1, int $operand2, string $operator): int { + + switch ($operator) { + case '+': + return $operand1 + $operand2; + case '-': + return $operand1 - $operand2; + default: + return 0; + } + + } + + /** + * Draws random lines on the given image. + * + * @param $image The image resource. + * @param int $count The number of lines to draw. + */ + private function drawRandomLines(&$image, $count) { + for ($i = 0; $i < $count; $i++) { + imageline( + $image, + mt_rand(0, self::WIDTH), + mt_rand(0, self::HEIGHT), + mt_rand(0, self::WIDTH), + mt_rand(0, self::HEIGHT), + imagecolorallocate( + $image, + mt_rand(200, 255), + mt_rand(200, 255), + mt_rand(200, 255) + ) + ); + } + } + + /** + * Get a random character from the given string. + * + * @param string $string The input string from which to extract a random character. + * + * @return string The randomly selected character from the input string. + */ + private function getRandomCharacter(string $string): string { + return substr($string, rand(0, strlen($string) - 1), 1); + } + + + /** + * Draw a character on the image with a random horizontal space. + * + * @param $image The image resource to draw on. + * @param int $x The initial x-coordinate for drawing the character. + * @param string $char The character to be drawn on the image. + * + * @return int The updated x-coordinate after drawing the character. + */ + private function drawCharacterWithRandomSpace(&$image, int $x, string $char): int { + $x += 10 + mt_rand(0, 10); + $this->updateImage($image, $x, $char); + return $x; + } + + + /** + * Add random noise to the image. + * + * @param $image The image resource to add noise to. + * @param int $noiseLevel The number of random pixels to add as noise. + * + * @return void + */ + private function addNoise(&$image, int $noiseLevel): void { + // Define noise color + $ns = imagecolorallocate($image, 200, 200, 200); + + // Add random pixels as noise + for ($i = 0; $i < $noiseLevel; $i++) { + for ($j = 0; $j < $noiseLevel; $j++) { + imagesetpixel( + $image, + rand(0, self::WIDTH - 1), // Adjusted range to avoid exceeding image dimensions + rand(0, self::HEIGHT - 1), // Adjusted range to avoid exceeding image dimensions + $ns + ); + } + } + } + + + /** + * Update session variables with provided numeric operands and operator symbols. + * + * @param string $captchaResult Captcha Result. + * + * @return void + */ + private function updateSession(float $captchaResult): void { + $this->session->set('captcha_result', $captchaResult); + } + + + /** + * Update the image with a character at the specified coordinates. + * + * @param $image The image resource to update. + * @param int $x The x-coordinate where the character will be placed. + * @param string $num The character to be added to the image. + * + * @return void + */ + private function updateImage(&$image, int $x, string $num): void { + // Generate a random color for the character + $color = imagecolorallocate($image, mt_rand(0, 155), mt_rand(0, 155), mt_rand(0, 155)); + + // Place the character on the image + imagechar( + $image, + mt_rand(4, 5), // Font size (randomly chosen between 4 and 5) + $x, + mt_rand(5, 20), // Vertical position (randomly chosen between 5 and 20) + $num, + $color + ); + } + + + +} diff --git a/lib/Service/LDAPConnectionService.php b/lib/Service/LDAPConnectionService.php index a45024e6dc9591868f7e7691c85b18cda4f14f0e..35537b56d86ad1bc28b68ea85f077fae1b7bd63b 100644 --- a/lib/Service/LDAPConnectionService.php +++ b/lib/Service/LDAPConnectionService.php @@ -5,21 +5,27 @@ declare(strict_types=1); namespace OCA\EcloudAccounts\Service; use Exception; +use OCA\User_LDAP\Configuration; +use OCA\User_LDAP\Helper; +use OCP\IConfig; use OCP\IUserManager; class LDAPConnectionService { /** @var IUserManager */ private $userManager; - private $configuration; - private $ldapEnabled; - private $access; + private $ldapConfig; + private IConfig $config; - public function __construct(IUserManager $userManager) { + public function __construct(IUserManager $userManager, Helper $ldapBackendHelper, IConfig $config) { $this->userManager = $userManager; $this->getConfigurationFromBackend(); + $ldapConfigPrefixes = $ldapBackendHelper->getServerConfigurationPrefixes(true); + $prefix = array_shift($ldapConfigPrefixes); + $this->ldapConfig = new Configuration($prefix); + $this->config = $config; } @@ -47,22 +53,13 @@ class LDAPConnectionService { return $backend->getBackendName() === 'LDAP'; } - public function isLDAPEnabled() : bool { + public function isLDAPEnabled(): bool { return $this->ldapEnabled; } public function username2dn(string $username) { return $this->access->username2dn($username); } - - - public function getUserBaseDn() : string { - if (isset($this->configuration['ldap_base_users'])) { - return $this->configuration['ldap_base_users']; - } - throw new Exception('User Base Dn not set!'); - } - public function getLDAPConnection() { if (!$this->ldapEnabled) { throw new Exception('LDAP backend is not enabled'); @@ -83,7 +80,7 @@ class LDAPConnectionService { return $conn; } - public function closeLDAPConnection($conn) : void { + public function closeLDAPConnection($conn): void { ldap_close($conn); } @@ -93,4 +90,18 @@ class LDAPConnectionService { } return $this->access; } + + public function getLDAPBaseUsers(): array { + $bases = $this->ldapConfig->ldapBaseUsers; + if (empty($bases)) { + $bases = $this->ldapConfig->ldapBase; + } + return $bases; + } + public function getDisplayNameAttribute(): string { + return $this->ldapConfig->ldapUserDisplayName; + } + public function getLdapQuota() { + return $this->config->getSystemValue('default_quota', '1024'); + } } diff --git a/lib/Service/UserService.php b/lib/Service/UserService.php index e74854d04a4d320ffd476f403c5b1041709a4d29..56e156fe925dda9306aec3250bf72393f04a1e2b 100644 --- a/lib/Service/UserService.php +++ b/lib/Service/UserService.php @@ -6,12 +6,14 @@ namespace OCA\EcloudAccounts\Service; require __DIR__ . '/../../vendor/autoload.php'; +use Exception; use OCA\EcloudAccounts\AppInfo\Application; use OCP\Defaults; use OCP\IConfig; use OCP\ILogger; use OCP\IUser; use OCP\IUserManager; +use OCP\L10N\IFactory; use OCP\Util; use Throwable; @@ -20,24 +22,41 @@ use UnexpectedValueException; class UserService { /** @var IUserManager */ private $userManager; - /** @var array */ private $appConfig; - /** @var IConfig */ private $config; - + /** @var CurlService */ private $curl; - private Defaults $defaults; - private ILogger $logger; + /** @var Defaults */ + private $defaults; + /** @var ILogger */ + private $logger; + /** @var IFactory */ + protected $l10nFactory; + /** @var array */ + private $apiConfig; + /** @var LDAPConnectionService */ + private $LDAPConnectionService; - public function __construct($appName, IUserManager $userManager, IConfig $config, CurlService $curlService, ILogger $logger, Defaults $defaults) { + public function __construct($appName, IUserManager $userManager, IConfig $config, CurlService $curlService, ILogger $logger, Defaults $defaults, IFactory $l10nFactory, LDAPConnectionService $LDAPConnectionService) { $this->userManager = $userManager; $this->config = $config; $this->appConfig = $this->config->getSystemValue($appName); $this->curl = $curlService; $this->logger = $logger; $this->defaults = $defaults; + $this->l10nFactory = $l10nFactory; + $this->LDAPConnectionService = $LDAPConnectionService; + $this->apiConfig = [ + 'mainDomain' => $this->config->getSystemValue('main_domain', ''), + 'commonApiUrl' => rtrim($this->config->getSystemValue('common_services_url', ''), '/') . '/', + 'commonServiceToken' => $this->config->getSystemValue('common_service_token', ''), + 'aliasDomain' => $this->config->getSystemValue('alias_domain', ''), + 'commonApiVersion' => $this->config->getSystemValue('common_api_version', ''), + 'userCluserId' => $this->config->getSystemValue('user_cluser_id', ''), + 'objectClass' => $this->config->getSystemValue('ldap_object_class', []), + ]; } @@ -48,7 +67,6 @@ class UserService { return $default; } - public function userExists(string $uid): bool { $exists = $this->userManager->userExists($uid); if ($exists) { @@ -69,16 +87,16 @@ class UserService { } public function getUser(string $uid): ?IUser { - return $this->userManager->get($uid); + if($this->userExists($uid)) { + return $this->userManager->get($uid); + } } - public function setRecoveryEmail(string $uid, string $recoveryEmail): bool { - try { - $this->config->setUserValue($uid, 'email-recovery', 'recovery-email', $recoveryEmail); - return true; - } catch (UnexpectedValueException $e) { - return false; - } + public function setRecoveryEmail(string $uid, string $recoveryEmail): void { + $this->config->setUserValue($uid, 'email-recovery', 'recovery-email', $recoveryEmail); + } + public function setTOS(string $uid, bool $tosAccepted): void { + $this->config->setUserValue($uid, 'terms_of_service', 'tosAccepted', intval($tosAccepted)); } public function getHMEAliasesFromConfig($uid) : array { @@ -138,7 +156,8 @@ class UserService { return null; } - public function sendWelcomeEmail(string $uid, string $toEmail) : void { + public function sendWelcomeEmail(string $displayname, string $username, string $userEmail, string $language = 'en') : void { + $sendgridAPIkey = $this->getSendGridAPIKey(); if (empty($sendgridAPIkey)) { $this->logger->warning("sendgrid_api_key is missing or empty.", ['app' => Application::APP_ID]); @@ -150,8 +169,7 @@ class UserService { $this->logger->warning("welcome_sendgrid_template_ids is missing or empty.", ['app' => Application::APP_ID]); return; } - - $language = $this->getUserLanguage($uid); + $templateID = $templateIDs['en']; if (isset($templateIDs[$language])) { $templateID = $templateIDs[$language]; @@ -160,15 +178,11 @@ class UserService { $fromEmail = Util::getDefaultEmailAddress('noreply'); $fromName = $this->defaults->getName(); - $user = $this->userManager->get($uid); - $toName = $user->getDisplayName(); - - $mainDomain = $this->getMainDomain(); try { - $email = $this->createSendGridEmail($fromEmail, $fromName, $toEmail, $toName, $templateID, $uid, $mainDomain); + $email = $this->createSendGridEmail($fromEmail, $fromName, $username, $displayname, $userEmail, $templateID); $this->sendEmailWithSendGrid($email, $sendgridAPIkey); } catch (Throwable $e) { - $this->logger->error('Error sending email to: ' . $toEmail . ': ' . $e->getMessage()); + $this->logger->error('Error sending welcome email to user: ' . $username . ': ' . $e->getMessage()); } } private function getSendGridAPIKey() : string { @@ -177,21 +191,23 @@ class UserService { private function getSendGridTemplateIDs() : array { return $this->config->getSystemValue('welcome_sendgrid_template_ids', ''); } - private function getMainDomain() : string { + public function getMainDomain() : string { return $this->config->getSystemValue('main_domain', ''); } - private function getUserLanguage(string $username) : string { - return $this->config->getUserValue($username, 'core', 'lang', 'en'); + public function setUserLanguage(string $username, string $language = 'en') { + $this->config->setUserValue($username, 'core', 'lang', $language); } - private function createSendGridEmail(string $fromEmail, string $fromName, string $toEmail, string $toName, string $templateID, string $username, string $mainDomain) : \SendGrid\Mail\Mail { + private function createSendGridEmail(string $fromEmail, string $fromName, string $username, string $displayname, string $userEmail, string $templateID) : \SendGrid\Mail\Mail { + $mainDomain = $this->getMainDomain(); + $email = new \SendGrid\Mail\Mail(); $email->setFrom($fromEmail, $fromName); - $email->addTo($toEmail, $toName); + $email->addTo($userEmail, $displayname); $email->setTemplateId($templateID); $email->addDynamicTemplateDatas([ "username" => $username, "mail_domain" => $mainDomain, - "display_name" => $toName + "display_name" => $displayname ]); return $email; } @@ -200,7 +216,177 @@ class UserService { $response = $sendgrid->send($email, [ CURLOPT_TIMEOUT => 15 ]); if ($response->statusCode() < 200 || $response->statusCode() > 299) { - throw new \Exception("SendGrid API error - Status Code: " . $response->statusCode()); + $this->logger->error("SendGrid API error - Status Code: " . $response->statusCode()); + } + } + /** + * Register a new user. + * + * @param string $displayname The display name of the user. + * @param string $recoveryemail The recovery email address for the user. + * @param string $username The chosen username for the user. + * @param string $userEmail The email address of the user. + * @param string $password The password chosen by the user. + * + * @return array An array containing information about the registered user. + * @throws Exception If the username or recovery email is already taken. + */ + public function registerUser(string $displayname, string $recoveryEmail, string $username, string $userEmail, string $password): array { + + if ($this->userExists($username)) { + throw new Exception("Username is already taken."); + } + if (!empty($recoveryEmail) && $this->checkRecoveryEmailAvailable($recoveryEmail)) { + throw new Exception("Recovery email address is already taken."); + } + return $this->addNewUserToLDAP($displayname, $recoveryEmail, $username, $userEmail, $password); + + } + /** + * Add a new user to the LDAP directory. + * + * @param string $displayname The display name of the new user. + * @param string $recoveryEmail The recovery email address of the new user. + * @param string $username The username of the new user. + * @param string $userEmail The email address of the new user. + * @param string $password The password of the new user. + * + * @return array Information about the added user. + * @throws Exception If there is an error while creating the Murena account. + */ + private function addNewUserToLDAP(string $displayname, string $recoveryEmail, string $username, string $userEmail, string $password): ?array { + try { + $connection = $this->LDAPConnectionService->getLDAPConnection(); + $base = $this->LDAPConnectionService->getLDAPBaseUsers()[0]; + + $newUserDN = "username=$username," . $base; + + $newUserEntry = [ + 'mailAddress' => $userEmail, + 'username' => $username, + 'usernameWithoutDomain' => $username, + 'userPassword' => $password, + 'displayName' => $displayname, + 'quota' => $this->LDAPConnectionService->getLdapQuota(), + 'recoveryMailAddress' => $recoveryEmail, + 'active' => 'TRUE', + 'mailActive' => 'TRUE', + 'userClusterID' => $this->apiConfig['userCluserId'], + 'objectClass' => $this->apiConfig['objectClass'] + ]; + + $ret = ldap_add($connection, $newUserDN, $newUserEntry); + + if (!$ret) { + throw new Exception("Error while creating Murena account."); + } + return $newUserEntry; + } catch (Exception $e) { + $this->logger->error('Error adding adding new user to LDAP: ' . $username . ': ' . $e->getMessage()); + return null; + } + } + /** + * Check if a recovery email address is available (not already taken by another user). + * + * @param string $recoveryEmail The recovery email address to check. + * + * @return bool True if the recovery email address is available, false otherwise. + */ + public function checkRecoveryEmailAvailable(string $recoveryEmail): bool { + $recoveryEmail = strtolower($recoveryEmail); + $users = $this->config->getUsersForUserValue('email-recovery', 'recovery-email', $recoveryEmail); + if(count($users)) { + return true; + } + return false; + } + + /** + * Create a Hide My Email (HME) alias for a user. + * + * @param string $username The username for which to create the HME alias. + * @param string $resultmail The email address associated with the HME alias. + * + * @return void + */ + public function createHMEAlias(string $username, string $resultmail): void { + $commonApiUrl = $this->apiConfig['commonApiUrl']; + $aliasDomain = $this->apiConfig['aliasDomain']; + $token = $this->apiConfig['commonServiceToken']; + $commonApiVersion = $this->apiConfig['commonApiVersion']; + + $endpoint = $commonApiVersion . '/aliases/hide-my-email/'; + $url = $commonApiUrl . $endpoint . $resultmail; + $data = array( + "domain" => $aliasDomain + ); + $headers = [ + "Authorization: Bearer $token" + ]; + $result = $this->curl->post($url, $data, $headers); + $result = json_decode($result, true); + + $hmeAlias = isset($result['emailAlias']) ? $result['emailAlias'] : ''; + if($hmeAlias != '') { + $hmeAliasAdded = $this->addHMEAliasInConfig($username, $hmeAlias); + if (!$hmeAliasAdded) { + $this->logger->error('Error adding HME Alias in config.'); + } } } + /** + * Create a new domain alias for a user. + * + * @param string $username The username for which to create the domain alias. + * @param string $userEmail The email address associated with the domain alias. + * + * @return mixed The result of the domain alias creation request, decoded from JSON. + */ + public function createNewDomainAlias(string $username, string $userEmail): mixed { + $commonApiUrl = $this->apiConfig['commonApiUrl']; + $commonApiVersion = $this->config->getSystemValue('commonApiVersion', ''); + $domain = $this->apiConfig['mainDomain']; + $token = $this->apiConfig['commonServiceToken']; + $commonApiVersion = $this->apiConfig['commonApiVersion']; + + $endpoint = $commonApiVersion . '/aliases/'; + $url = $commonApiUrl . $endpoint . $userEmail; + + $data = array( + "alias" => $username, + "domain" => $domain + ); + $headers = [ + "Authorization: Bearer $token" + ]; + + $result = $this->curl->post($url, $data, $headers); + $result = json_decode($result, true); + return $result; + } + /** + * Set account data locally for a user. + * + * @param string $uid The unique identifier of the user. + * @param string $mailAddress The email address to set for the user. + * @param string $quota The quota to set for the user (in megabytes). + * + * @return void + */ + public function setAccountDataLocally(string $uid, string $mailAddress, string $quota): void { + + $user = $this->getUser($uid); + if (is_null($user)) { + $this->logger->error('User not found'); + return; + } + + // Set the email address for the user + $user->setEMailAddress($mailAddress); + + // Format and set the quota for the user (in megabytes) + $quota = strval($quota) . ' MB'; + $user->setQuota($quota); + } } diff --git a/package-lock.json b/package-lock.json index 5077e57f6351ca83850c3c64718d8e88400480d3..6a1bd3d6a819945298b8eeebbcee46bb731d4660 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,8 @@ "@nextcloud/l10n": "^1.6.0", "@nextcloud/router": "^2.0.0", "@nextcloud/vue": "^5.4.0", - "vue": "^2.7.0" + "vue": "^2.7.0", + "vue-password-strength-meter": "^1.7.2" }, "devDependencies": { "@nextcloud/babel-config": "^1.0.0", @@ -11137,6 +11138,18 @@ "resolved": "https://registry.npmjs.org/vue-observe-visibility/-/vue-observe-visibility-0.4.6.tgz", "integrity": "sha512-xo0CEVdkjSjhJoDdLSvoZoQrw/H2BlzB5jrCBKGZNXN2zdZgMuZ9BKrxXDjNP2AxlcCoKc8OahI3F3r3JGLv2Q==" }, + "node_modules/vue-password-strength-meter": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/vue-password-strength-meter/-/vue-password-strength-meter-1.7.2.tgz", + "integrity": "sha512-S17DsEbXciTcI0fKq1PXia7i3COM/AJOT7os8AVyGxglc5ho1EBssE+ogDa5cFh6Rn8pPgqfVCt/KBcNpgPBiA==", + "engines": { + "node": ">= 4.0.0", + "npm": ">= 3.0.0" + }, + "peerDependencies": { + "zxcvbn": "^4.4.2" + } + }, "node_modules/vue-resize": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/vue-resize/-/vue-resize-1.0.1.tgz", @@ -11796,6 +11809,12 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zxcvbn": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/zxcvbn/-/zxcvbn-4.4.2.tgz", + "integrity": "sha512-Bq0B+ixT/DMyG8kgX2xWcI5jUvCwqrMxSFam7m0lAf78nf04hv6lNCsyLYdyYTrCVMqNDY/206K7eExYCeSyUQ==", + "peer": true } }, "dependencies": { @@ -20246,6 +20265,12 @@ "resolved": "https://registry.npmjs.org/vue-observe-visibility/-/vue-observe-visibility-0.4.6.tgz", "integrity": "sha512-xo0CEVdkjSjhJoDdLSvoZoQrw/H2BlzB5jrCBKGZNXN2zdZgMuZ9BKrxXDjNP2AxlcCoKc8OahI3F3r3JGLv2Q==" }, + "vue-password-strength-meter": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/vue-password-strength-meter/-/vue-password-strength-meter-1.7.2.tgz", + "integrity": "sha512-S17DsEbXciTcI0fKq1PXia7i3COM/AJOT7os8AVyGxglc5ho1EBssE+ogDa5cFh6Rn8pPgqfVCt/KBcNpgPBiA==", + "requires": {} + }, "vue-resize": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/vue-resize/-/vue-resize-1.0.1.tgz", @@ -20724,6 +20749,12 @@ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "peer": true + }, + "zxcvbn": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/zxcvbn/-/zxcvbn-4.4.2.tgz", + "integrity": "sha512-Bq0B+ixT/DMyG8kgX2xWcI5jUvCwqrMxSFam7m0lAf78nf04hv6lNCsyLYdyYTrCVMqNDY/206K7eExYCeSyUQ==", + "peer": true } } } diff --git a/package.json b/package.json index 83b8a14f52ca558912e46b633968d3c5a1b20a08..74dc503cdd9b43742dab1ccd8b6da31a74f6dcb2 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,8 @@ "private": true, "scripts": { "build": "NODE_ENV=production webpack --progress", - "dev": "NODE_ENV=development webpack --progress", - "watch": "NODE_ENV=development webpack --progress --watch", + "dev": "NODE_ENV=development webpack --progress", + "watch": "NODE_ENV=development webpack --progress --watch", "lint": "eslint --ext .js,.vue src", "lint:fix": "eslint --ext .js,.vue src --fix", "stylelint": "stylelint {src,css}/**/{*.scss,*.css} --allow-empty-input", @@ -23,7 +23,8 @@ "@nextcloud/l10n": "^1.6.0", "@nextcloud/router": "^2.0.0", "@nextcloud/vue": "^5.4.0", - "vue": "^2.7.0" + "vue": "^2.7.0", + "vue-password-strength-meter": "^1.7.2" }, "browserslist": [ "extends @nextcloud/browserslist-config" diff --git a/src/Signup.vue b/src/Signup.vue new file mode 100644 index 0000000000000000000000000000000000000000..bfb682eae8a03a989a8676fd112ccdba1900fae1 --- /dev/null +++ b/src/Signup.vue @@ -0,0 +1,115 @@ + + + + diff --git a/src/signup.js b/src/signup.js new file mode 100644 index 0000000000000000000000000000000000000000..591ad0f555f7d709268c467ebc8220fe2942ae75 --- /dev/null +++ b/src/signup.js @@ -0,0 +1,8 @@ +import Vue from 'vue' +import './common.js' +import Signup from './Signup.vue' + +export default new Vue({ + el: '#ecloud-accounts-signup', + render: h => h(Signup), +}) diff --git a/src/signup/CaptchaForm.vue b/src/signup/CaptchaForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..e12a1e3c39b701e34699ba7e0dd1db464b1eebcb --- /dev/null +++ b/src/signup/CaptchaForm.vue @@ -0,0 +1,246 @@ + + + + diff --git a/src/signup/RecoveryEmailForm.vue b/src/signup/RecoveryEmailForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..c0377db45e26b6155336ad0d6bdedfd915ec39f3 --- /dev/null +++ b/src/signup/RecoveryEmailForm.vue @@ -0,0 +1,270 @@ + + + + diff --git a/src/signup/RegistrationForm.vue b/src/signup/RegistrationForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..38faebc25cf994be149da6b5cf2db6fe693b5fa8 --- /dev/null +++ b/src/signup/RegistrationForm.vue @@ -0,0 +1,531 @@ + + + + + diff --git a/src/signup/SuccessSection.vue b/src/signup/SuccessSection.vue new file mode 100644 index 0000000000000000000000000000000000000000..9414501d28bd0e2ba1bff41b2b7444ddf071f263 --- /dev/null +++ b/src/signup/SuccessSection.vue @@ -0,0 +1,93 @@ + + + + diff --git a/templates/signup.php b/templates/signup.php new file mode 100644 index 0000000000000000000000000000000000000000..95f0ce04468710a6f32280cb66cbaa75e5ee00c5 --- /dev/null +++ b/templates/signup.php @@ -0,0 +1,4 @@ + +
\ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index b2b5fbb06e3dab6d263b633238ec3df4ac9b523d..b6eaacbad759d6b97381556dbaa35e18123dbeca 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -9,5 +9,6 @@ module.exports = { 'delete-shop-account-setting': path.join(__dirname, 'src/delete-shop-account-setting.js'), 'delete-account-listeners': path.join(__dirname, 'src/delete-account-listeners.js'), 'beta-user-setting': path.join(__dirname, 'src/beta-user-setting.js'), + 'signup': path.join(__dirname, 'src/signup.js') }, }