From fea291b72e5756ffe00b8529144a383c066a4830 Mon Sep 17 00:00:00 2001 From: yu-i-i Date: Fri, 10 Jul 2026 02:08:40 +0200 Subject: [PATCH] Registration page: the initial commit --- server-ce/config/settings.js | 2 + .../CollaboratorsInviteController.mjs | 13 ++- .../web/app/src/infrastructure/Features.mjs | 6 +- services/web/config/settings.defaults.js | 1 + services/web/locales/de.json | 6 ++ services/web/locales/en.json | 6 ++ services/web/locales/ru.json | 6 ++ .../app/src/RegistrationPageController.mjs | 101 ++++++++++++++++++ .../app/src/RegistrationPageRouter.mjs | 33 ++++++ .../app/src/UserRegistrationHandler.mjs | 51 +++++++++ .../registration-page/app/src/tsconfig.json | 1 + .../registration-page/app/views/register.pug | 98 +++++++++++++++++ .../web/modules/registration-page/index.mjs | 30 ++++++ 13 files changed, 349 insertions(+), 5 deletions(-) create mode 100644 services/web/modules/registration-page/app/src/RegistrationPageController.mjs create mode 100644 services/web/modules/registration-page/app/src/RegistrationPageRouter.mjs create mode 100644 services/web/modules/registration-page/app/src/UserRegistrationHandler.mjs create mode 100644 services/web/modules/registration-page/app/src/tsconfig.json create mode 100644 services/web/modules/registration-page/app/views/register.pug create mode 100644 services/web/modules/registration-page/index.mjs diff --git a/server-ce/config/settings.js b/server-ce/config/settings.js index 620c696695..ad15a8ffd8 100644 --- a/server-ce/config/settings.js +++ b/server-ce/config/settings.js @@ -193,6 +193,8 @@ const settings = { maintenanceMessage: process.env.OVERLEAF_MAINTENANCE_MESSAGE, maintenanceMessageHTML: process.env.OVERLEAF_MAINTENANCE_MESSAGE_HTML, + enableRegiserPage: process.env.OVERLEAF_ENABLE_REGISTER_PAGE === 'true', + // The name this is used to describe your Overleaf Community Edition Installation appName: process.env.OVERLEAF_APP_NAME || 'Overleaf Community Edition', diff --git a/services/web/app/src/Features/Collaborators/CollaboratorsInviteController.mjs b/services/web/app/src/Features/Collaborators/CollaboratorsInviteController.mjs index 7543b42fb1..06997a5366 100644 --- a/services/web/app/src/Features/Collaborators/CollaboratorsInviteController.mjs +++ b/services/web/app/src/Features/Collaborators/CollaboratorsInviteController.mjs @@ -11,6 +11,7 @@ import EmailHelper from '../Helpers/EmailHelper.mjs' import EditorRealTimeController from '../Editor/EditorRealTimeController.mjs' import AnalyticsManager from '../Analytics/AnalyticsManager.mjs' import SessionManager from '../Authentication/SessionManager.mjs' +import Features from '../../infrastructure/Features.mjs' import { RateLimiter } from '../../infrastructure/RateLimiter.mjs' import { z, zz, parseReq } from '../../infrastructure/Validation.mjs' import { expressify } from '@overleaf/promise-utils' @@ -386,7 +387,11 @@ async function viewInvite(req, res) { user_first_name: owner.first_name, } AuthenticationController.setRedirectInSession(req) - return res.redirect('/register') + if (Features.hasFeature('registration-page')) { + return res.redirect('/register') + } else { + return res.redirect('/login') + } } // cleanup if set for register page @@ -442,6 +447,12 @@ async function viewSharingLink(req, res) { if (!isPublicSharingLink) { AuthenticationController.setRedirectInSession(req) return res.redirect('/register') + AuthenticationController.setRedirectInSession(req) + if (Features.hasFeature('registration-page')) { + return res.redirect('/register') + } else { + return res.redirect('/login') + } } } diff --git a/services/web/app/src/infrastructure/Features.mjs b/services/web/app/src/infrastructure/Features.mjs index 2c1393d6bc..2a1a7a38e2 100644 --- a/services/web/app/src/infrastructure/Features.mjs +++ b/services/web/app/src/infrastructure/Features.mjs @@ -34,6 +34,7 @@ const Features = { return ( (Boolean(Settings.ldap) && Boolean(Settings.ldap.enable)) || (Boolean(Settings.saml) && Boolean(Settings.saml.enable)) || + (Boolean(Settings.oidc) && Boolean(Settings.oidc.enable)) || Boolean(Settings.overleaf) ) }, @@ -51,10 +52,7 @@ const Features = { case 'homepage': return Boolean(Settings.enableHomepage) case 'registration-page': - return ( - !Features.externalAuthenticationSystemUsed() || - Boolean(Settings.overleaf) || Settings.oidc?.allowedOIDCEmailDomains - ) + return Boolean(Settings.enableRegistrationPage) case 'registration': return Boolean(Settings.overleaf) case 'chat': diff --git a/services/web/config/settings.defaults.js b/services/web/config/settings.defaults.js index edf623c288..eebff55984 100644 --- a/services/web/config/settings.defaults.js +++ b/services/web/config/settings.defaults.js @@ -1187,6 +1187,7 @@ module.exports = { 'authentication/saml', 'authentication/oidc', 'admin-tools', // import after authentication + 'registration-page', // import after authentication 'template-gallery', 'git-bridge', 'github-sync', diff --git a/services/web/locales/de.json b/services/web/locales/de.json index 7d067bc861..a472d910e7 100644 --- a/services/web/locales/de.json +++ b/services/web/locales/de.json @@ -75,6 +75,7 @@ "account_not_linked_to_dropbox": "Dein Konto ist nicht mit Dropbox verknüpft", "account_settings": "Kontoeinstellungen", "account_with_email_exists": "Anscheinend existiert bereits ein __appName__-Konto mit der E-Mail-Adresse __email__.", + "account_with_this_email_exists": "Anscheinend existiert bereits ein __appName__-Konto mit dieser E-Mail-Adresse.", "acct_linked_to_institution_acct_2": "Du kannst dich jetzt mit dem Konto deiner Institution <0>__institutionName__ bei Overleaf anmelden.", "actions": "Aktionen", "activate": "Aktivieren", @@ -572,6 +573,7 @@ "export_project_to_github": "Projekt nach GitHub exportieren", "failed_to_publish_as_a_template": "Veröffentlichung als Vorlage fehlgeschlagen.", "failed_to_send_managed_user_invite_to_email": "Der Versand der Einladung für Verwaltete Benutzer an <0>__email__ hat nicht funktioniert. Bitte versuche es später noch einmal.", + "failed_to_send_registration_email": "Die Registrierungs-E-Mail konnte nicht gesendet werden. Bitte überprüfe die E-Mail-Adresse.", "faq_how_does_free_trial_works_answer": "Während deines __len__-tägigen Probe-Abonnements erhältst du vollen Zugriff auf die Funktionen des von dir gewählten __appName__-Abonnements. Es besteht keine Verpflichtung, über die Testperiode hinaus fortzufahren. Deine Karte wird am Ende des __len__-tägigen Testzeitraums belastet, sofern du nicht vorher gekündigt hast. Um zu kündigen, gehe zu deinen Abonnementeinstellungen in deinem Konto.", "fast": "Schnell", "fast_draft": "Schnell [Entwurf]", @@ -875,6 +877,7 @@ "include_caption": "Beschriftung anzeigen", "include_label": "Label anzeigen", "included_as_part_of_your_writefull_subscription": "Als Teil deines Writefull-Abonnements enthalten", + "including_all_subdomains": "einschließlich aller Subdomains", "increased_compile_timeout": "Zeitlimit beim Kompilieren erhöhen", "info": "Info", "inr_discount_modal_info": "Erhalte Dokumentenverlauf, Änderungsverfolgung, weitere Mitarbeiter und mehr zu Preisen nach Kaufkraftparität.", @@ -1390,11 +1393,13 @@ "regards": "Viele Grüße", "register": "Registrieren", "register_error": "Registrierungsfehler", + "register_success": "Registrierung erfolgreich. Bitte überprüfen Sie Ihre E-Mail, um Ihr Konto zu aktivieren.", "register_intercept_sso": "Du kannst dein __authProviderName__-Konto nach der Anmeldung auf der Seite Kontoeinstellungen verknüpfen.", "register_to_edit_template": "Bitte registriere dich um die __templateName__ Vorlage zu bearbeiten", "register_with_another_email": "Registriere Dich bei __appName__ mit einer anderen E-Mail-Adresse.", "registered": "Registriert", "registering": "Registrieren", + "registration_is_restricted_to_the_following_email_domains": "Die Registrierung ist auf die folgenden E-Mail-Domänen beschränkt:", "reject": "Verwerfen", "related_tags": "Ähnliche Stichwörter", "relink_your_account": "Verknüpfe dein Konto neu", @@ -1575,6 +1580,7 @@ "stops_compiling_after_the_first_error_so_you_can_fix_issues_one_at_a_time": "Stoppt das Kompilieren beim ersten Fehler, damit du Probleme einzeln beheben kannst", "store_your_work": "Speichere deine Arbeit auf deiner eigenen Infrastruktur", "student": "Student", + "subdomains_of_this_domain_only": "nur Subdomains dieser Domain", "subject": "Betreff", "submit": "Absenden", "submit_title": "Einreichen", diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 0dde85c1f0..32b1de603d 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -86,6 +86,7 @@ "account_not_linked_to_dropbox": "Your account is not linked to Dropbox", "account_settings": "Account settings", "account_with_email_exists": "It looks like an __appName__ account with the email __email__ already exists.", + "account_with_this_email_exists": "It looks like an __appName__ account with this email address already exists.", "acct_linked_to_institution_acct_2": "You can <0>log in to Overleaf through your <0>__institutionName__ institutional login.", "action_needed": "Action needed", "actions": "Actions", @@ -928,6 +929,7 @@ "failed_to_publish_as_a_template": "Failed to publish as a template.", "failed_to_send_group_invite_to_email": "Failed to send group invite to <0>__email__. Please try again later.", "failed_to_send_managed_user_invite_to_email": "Failed to send Managed User invite to <0>__email__. Please try again later.", + "failed_to_send_registration_email": "Failed to send the registration email. Please check the email address.", "failed_to_send_sso_link_invite_to_email": "Failed to send SSO invite reminder to <0>__email__. Please try again later.", "fair_usage_policy_applies": "Fair usage policy applies.", "faq_how_does_free_trial_works_answer": "You get full access to your chosen __appName__ plan during your __len__-day free trial. There is no obligation to continue beyond the trial. Your card will be charged at the end of your __len__ day trial unless you cancel before then. You can cancel via your subscription settings.", @@ -1380,6 +1382,7 @@ "include_the_error_message_and_ai_response": "Include the error message and AI response", "included_as_part_of_your_writefull_subscription": "Included as part of your Writefull subscription", "includes": "includes", + "including_all_subdomains": "including all subdomains", "increase_indent": "Increase indentation", "increased_ai_allowance": "Increased AI allowance", "increased_ai_allowance_tooltip": "10 AI uses per day across all AI tools.", @@ -2331,12 +2334,14 @@ "regards": "Regards", "register": "Register", "register_error": "Registration error", + "register_success": "Registration successful. Please check your email to activate your account.", "register_intercept_sso": "You can link your __authProviderName__ account from the Account settings page after logging in.", "register_to_accept_invitation": "Register to accept invitation", "register_to_edit_template": "Please register to edit the __templateName__ template", "register_with_another_email": "Register with __appName__ using another email.", "registered": "Registered", "registering": "Registering", + "registration_is_restricted_to_the_following_email_domains": "The registration is restricted to the following email domains:", "reject": "Reject", "reject_change": "Reject change", "reject_selected_changes": "Reject selected changes", @@ -2786,6 +2791,7 @@ "student_verification_rate_limited_info": "We’ve had too many verification requests recently. Please wait a few minutes and try again.", "student_verification_required": "Student verification required", "students": "Students", + "subdomains_of_this_domain_only": "subdomains of this domain only", "subject": "Subject", "subject_area": "Subject area", "submit": "submit", diff --git a/services/web/locales/ru.json b/services/web/locales/ru.json index 5d00f09a45..02e63f43f9 100644 --- a/services/web/locales/ru.json +++ b/services/web/locales/ru.json @@ -48,6 +48,7 @@ "account_information": "Информация об аккаунте", "account_not_linked_to_dropbox": "Ваш аккаунт не синхронизирован с Dropbox", "account_settings": "Настройки профиля", + "account_with_this_email_exists": "Похоже, что учётная запись __appName__ с этим адресом электронной почты уже существует.", "actions": "Действия", "activate": "Активировать", "activate_account": "Активируйте Ваш аккаунт", @@ -224,6 +225,7 @@ "explore_plans": "Посмотреть тарифы", "export_project_to_github": "Экспорт проекта на GitHub", "failed_to_publish_as_a_template": "Не удалось создать шаблон.", + "failed_to_send_registration_email": "Не удалось отправить письмо для подтверждения регистрации. Пожалуйста, проверьте адрес электронной почты.", "fast": "быстрый", "feature_controls": "Управление функциями", "feature_settings_subtext": "Просмотр и управление функциями, доступными участникам Вашей группы", @@ -377,6 +379,7 @@ "importing_and_merging_changes_in_github": "Импорт и слияние изменений в GitHub", "inactive_projects": "Неактивные проекты", "included_as_part_of_your_writefull_subscription": "Входит в Вашу подписку Writefull", + "including_all_subdomains": "включая все поддомены", "info": "Информация", "institution": "Организация", "integrations_like_github": "Интеграции, такие как синхронизация с GitHub", @@ -599,9 +602,11 @@ "refresh_page_after_starting_free_trial": "Пожалуйста, обновите страницу после начала бесплатного пробного периода.", "regards": "С уважением", "register": "Регистрация", + "register_success": "Регистрация успешно завершена. Пожалуйста, проверьте свою электронную почту, чтобы активировать учётную запись.", "register_to_edit_template": "Пожалуйста, зарегистрируйтесь, чтобы редактировать шаблон __templateName__", "registered": "Зарегистрирован", "registering": "Создание аккаунта", + "registration_is_restricted_to_the_following_email_domains": "Регистрация разрешена только для следующих доменов электронной почты:", "remind_before_trial_ends": "Мы напомним Вам до окончания пробного периода", "remove_from_group": "Удалить из группы", "removed": "удалено", @@ -684,6 +689,7 @@ "stop_compile": "Остановить компиляцию", "stop_on_validation_error": "Проверить синтаксис перед компиляцией", "student": "Студент", + "subdomains_of_this_domain_only": "только поддомены этого домена", "subject": "Тема", "subscribe": "Подписаться", "subscribe_now": "Подписаться сейчас", diff --git a/services/web/modules/registration-page/app/src/RegistrationPageController.mjs b/services/web/modules/registration-page/app/src/RegistrationPageController.mjs new file mode 100644 index 0000000000..76337766f4 --- /dev/null +++ b/services/web/modules/registration-page/app/src/RegistrationPageController.mjs @@ -0,0 +1,101 @@ +import Path from 'path' +import logger from '@overleaf/logger' +import registerNewUserAndSendActivationEmail from './UserRegistrationHandler.mjs' +import EmailHelper from '../../../../app/src/Features/Helpers/EmailHelper.mjs' +import Settings from '@overleaf/settings' + +async function registrationPage(req, res, next) { + // Check if the user is already logged in + if (req.user != null) { + return res.redirect(`/`) + } + + const sharedProjectData = req.session.sharedProjectData || {} + + const newTemplateData = {} + if (req.session.templateData != null) { + newTemplateData.templateName = req.session.templateData.templateName + } + + const allowedDomains = Settings.allowedRegistrationEmailDomains || [] + const displayDomains = new Map() + + for (const domain of allowedDomains) { + const subdomainsOnly = domain.startsWith('*.') + const base = subdomainsOnly ? domain.substring(2) : domain + + const existing = displayDomains.get(base) + + displayDomains.set(base, { + domain: base, + exact: existing?.exact || !subdomainsOnly, + subdomains: existing?.subdomains || subdomainsOnly, + }) + } + + const __dirname = Path.dirname(new URL(import.meta.url).pathname) + res.render(Path.resolve(__dirname, '../views/register'), { + title: 'register', + sharedProjectData, + newTemplateData, + displayDomains: [...displayDomains.values()], + csrfToken: req.csrfToken(), + }) +} + +async function registerNewUser(req, res, next) { + const { email, first_name, last_name } = req.body + + if ( + typeof first_name !== 'string' || first_name.length > 100 || + typeof last_name !== 'string' || last_name.length > 100 + ) { + return res.status(400).json({ message: 'Too long name.' }) + } + + const parsedEmail = EmailHelper.parseEmail(email) + if (!parsedEmail) { + return res.status(400).json({ message: 'Invalid email address.' }) + } + + // If registration is restricted to a specific email domains, + // check that the email domain is allowed + const domain = parsedEmail.split('@').pop() + const allowedDomains = Settings.allowedRegistrationEmailDomains + + if ( + allowedDomains && + !allowedDomains.some(pattern => { + if (pattern.startsWith('*.')) { + const base = pattern.slice(2) + return domain.endsWith(`.${base}`) + } + return domain === pattern + }) + ) { + return res.status(403).json({ + message: 'Registration is not available for this email domain.', + }) + } + + const t = req.i18n.translate + try { + const userDetails = { email, first_name, last_name } + const success = await registerNewUserAndSendActivationEmail(userDetails) + if (success) { + return res.status(200).json({ message: t('register_success') }) + } else { + return res.status(409).json({ message: { key: 'account_with_this_email_exists' } }) + } + } catch (error) { + if (error.message === 'error sending message') { + return res.status(422).json({ message: t('failed_to_send_registration_email') }) + } + return next(error) + } +} + +export default { + registrationPage, + registerNewUser +} diff --git a/services/web/modules/registration-page/app/src/RegistrationPageRouter.mjs b/services/web/modules/registration-page/app/src/RegistrationPageRouter.mjs new file mode 100644 index 0000000000..4ee2c86bc8 --- /dev/null +++ b/services/web/modules/registration-page/app/src/RegistrationPageRouter.mjs @@ -0,0 +1,33 @@ +import logger from "@overleaf/logger" +import Settings from "@overleaf/settings" +import RegistrationPageController from './RegistrationPageController.mjs' +import AuthenticationController from '../../../../app/src/Features/Authentication/AuthenticationController.mjs' +import RateLimiterMiddleware from "../../../../app/src/Features/Security/RateLimiterMiddleware.mjs" +import { RateLimiter } from "../../../../app/src/infrastructure/RateLimiter.mjs" + +// Limit registration attempts to 3 per minute per IP +const registrationRateLimiters = { + postRegister: new RateLimiter('postRegister', { + points: 5, + duration: 60, + }), +} + +export default { + apply(webRouter) { + logger.debug({}, 'Init Registration Page module') + + // remove default registration router if it exists + webRouter.stack = webRouter.stack.filter(layer => { + return !(layer.route && layer.route.path === '/register' && layer.route.methods.get) + }) + + webRouter.get('/register', RegistrationPageController.registrationPage) + + webRouter.post( + '/register', + RateLimiterMiddleware.rateLimit(registrationRateLimiters.postRegister), + RegistrationPageController.registerNewUser + ) + } +} diff --git a/services/web/modules/registration-page/app/src/UserRegistrationHandler.mjs b/services/web/modules/registration-page/app/src/UserRegistrationHandler.mjs new file mode 100644 index 0000000000..5a067aad54 --- /dev/null +++ b/services/web/modules/registration-page/app/src/UserRegistrationHandler.mjs @@ -0,0 +1,51 @@ +import crypto from 'node:crypto' +import settings from '@overleaf/settings' +import logger from '@overleaf/logger' +import UserRegistrationHandler from '../../../../app/src/Features/User/UserRegistrationHandler.mjs' +import UserDeleter from '../../../../app/src/Features/User/UserDeleter.mjs' +import EmailHandler from '../../../../app/src/Features/Email/EmailHandler.mjs' +import OneTimeTokenHandler from '../../../../app/src/Features/Security/OneTimeTokenHandler.mjs' + +const ONE_WEEK = 7 * 24 * 60 * 60 // seconds + +export default async function registerNewUserAndSendActivationEmail(userData) { + let user + try { + const password = crypto.randomBytes(32).toString('hex') + user = await UserRegistrationHandler.promises.registerNewUser({ ...userData, password }) + } catch (error) { + if (error.message === 'EmailAlreadyRegistered') { + logger.debug({ email: userData.email }, 'user already registered') + return false + } + throw error + } + + try { + const token = await OneTimeTokenHandler.promises.getNewToken( + 'password', + { user_id: user._id.toString(), email: user.email }, + { expiresIn: ONE_WEEK } + ) + + const setNewPasswordUrl = `${settings.siteUrl}/user/activate?token=${token}&user_id=${user._id}` + + await EmailHandler.promises.sendEmail('registered', { + to: user.email, + setNewPasswordUrl, + }) + } catch (error) { + try { + await UserDeleter.promises.deleteUser(user._id, { + ipAddress: '127.0.0.1', + skipEmail: true, + }) + await UserDeleter.promises.expireDeletedUser(user._id) + } catch (err) { + logger.error({ err, userId: user._id }, 'failed delete user') + } + throw error + } + + return true +} diff --git a/services/web/modules/registration-page/app/src/tsconfig.json b/services/web/modules/registration-page/app/src/tsconfig.json new file mode 100644 index 0000000000..ddee721c50 --- /dev/null +++ b/services/web/modules/registration-page/app/src/tsconfig.json @@ -0,0 +1 @@ +{ "extends": "../../../../tsconfig.backend.json" } diff --git a/services/web/modules/registration-page/app/views/register.pug b/services/web/modules/registration-page/app/views/register.pug new file mode 100644 index 0000000000..99874ef8f2 --- /dev/null +++ b/services/web/modules/registration-page/app/views/register.pug @@ -0,0 +1,98 @@ +extends ../../../../app/views/layout-website-redesign + +block vars + - isWebsiteRedesign = true + +block content + main#main-content.content + .container + .row + .registration-message + if sharedProjectData.user_first_name !== undefined + h1 #{translate("user_wants_you_to_see_project", {username:sharedProjectData.user_first_name, projectname:""})} + em #{sharedProjectData.project_name} + div + | #{translate("join_sl_to_view_project")}. + div + | #{translate("already_have_sl_account")} + | + a(href='/login') #{translate("login_here")} + else if newTemplateData.templateName !== undefined + h1 #{translate("register_to_edit_template", {templateName:newTemplateData.templateName})} + + div #{translate("already_have_sl_account")} + | + a(href='/login') #{translate("login_here")} + .row + .col-lg-8.offset-lg-2.col-xl-6.offset-xl-3 + .page-header + h1.login-register-header-heading-focus.h3 #{translate("register")} + if displayDomains.length + .alert.alert-info + div.small.fst-italic.mb-2 #{translate("registration_is_restricted_to_the_following_email_domains")} + ul.mb-0.small + each item in displayDomains + li + span.small.fst-italic + code #{item.domain} + if item.subdomains && item.exact + | (#{translate("including_all_subdomains")}) + else if item.subdomains + | (#{translate("subdomains_of_this_domain_only")}) + form( + data-ol-async-form + name="registrationForm" + action="/register", + method="POST", + aria-label=translate('create_account') + ) + input(name='_csrf' type='hidden' value=csrfToken) + +formMessagesNewStyle() + +customFormMessageNewStyle('account_with_this_email_exists', 'warn') + | !{translate("account_with_this_email_exists")} + a.d-block.mt-1(href='/user/password/reset') #{translate("forgot_your_password")}? + .form-group + label(for='firstNameField') #{translate("first_name")} + input.form-control( + aria-label="first_name" + id='firstNameField' + type='text', + name='first_name', + placeholder=translate("first_name"), + required, + autocomplete="given-name", + maxlength="100" + autofocus + ) + .form-group + label(for='lastNameField') #{translate("last_name")} + input.form-control( + aria-label="last_name" + id='lastNameField' + type='text', + name='last_name', + placeholder=translate("last_name"), + required, + autocomplete="family-name", + maxlength="100" + ) + .form-group + label(for='emailField') #{translate("email")} + input.form-control( + aria-label="email" + id='emailField' + type='email', + name='email', + placeholder=translate("email"), + required, + autocomplete="username", + maxlength="100" + ) + div(style="margin-top: 3rem") + .actions + button.btn.btn-primary.w-100( + type='submit', + data-ol-disabled-inflight + ) + span(data-ol-inflight="idle") #{translate("create_account")} + span(hidden data-ol-inflight="pending") #{translate("registering")}… diff --git a/services/web/modules/registration-page/index.mjs b/services/web/modules/registration-page/index.mjs new file mode 100644 index 0000000000..685cc49e8f --- /dev/null +++ b/services/web/modules/registration-page/index.mjs @@ -0,0 +1,30 @@ +import Settings from '@overleaf/settings' +import { boolFromEnv } from '../authentication/utils.mjs' + +let RegistrationPageModule = {} + +let enableRegistrationPage = boolFromEnv(process.env.OVERLEAF_ENABLE_REGISTRATION_PAGE) + +// if the env var is not set, enable registration page iff the external authentication is not enabled +if (enableRegistrationPage === undefined) { + enableRegistrationPage = !(Settings.ldap?.enable || Settings.saml?.enable || Settings.oidc?.enable) +} + +if (enableRegistrationPage) { + const { default: router } = await import('./app/src/RegistrationPageRouter.mjs') + + Settings.enableRegistrationPage = true + + if (process.env.OVERLEAF_ALLOWED_REGISTRATION_EMAIL_DOMAINS) { + Settings.allowedRegistrationEmailDomains = process.env.OVERLEAF_ALLOWED_REGISTRATION_EMAIL_DOMAINS + .split(/[,\s]+/) + .filter(Boolean) + } + + RegistrationPageModule = { + name: 'registration-page', + router: router, + } +} + +export default RegistrationPageModule