diff --git a/services/web/app/src/Features/User/UserPagesController.mjs b/services/web/app/src/Features/User/UserPagesController.mjs
index 16774e307d..e70f9b92a8 100644
--- a/services/web/app/src/Features/User/UserPagesController.mjs
+++ b/services/web/app/src/Features/User/UserPagesController.mjs
@@ -191,6 +191,14 @@ async function accountSuspended(req, res) {
})
}
+async function logout(req, res) {
+ const isLoggedIn = SessionManager.isUserLoggedIn(req.session)
+ if (!isLoggedIn) {
+ return res.redirect('/')
+ }
+ res.render('user/logout')
+}
+
async function reconfirmAccountPage(req, res) {
const pageData = {
reconfirm_email: req.session.reconfirm_email,
@@ -234,6 +242,7 @@ async function emailPreferencesPage(req, res) {
const UserPagesController = {
accountSuspended: expressify(accountSuspended),
+ logout: expressify(logout),
registerPage(req, res) {
const sharedProjectData = req.session.sharedProjectData || {}
diff --git a/services/web/app/src/router.mjs b/services/web/app/src/router.mjs
index f1c36d2f4c..938f8ac877 100644
--- a/services/web/app/src/router.mjs
+++ b/services/web/app/src/router.mjs
@@ -258,6 +258,7 @@ async function initialize(webRouter, privateApiRouter, publicApiRouter) {
'/read-only/one-time-login'
)
+ webRouter.get('/logout', UserPagesController.logout)
webRouter.post('/logout', UserController.logout)
webRouter.get('/restricted', AuthorizationMiddleware.restricted)
diff --git a/services/web/app/views/user/logout.pug b/services/web/app/views/user/logout.pug
new file mode 100644
index 0000000000..f588283547
--- /dev/null
+++ b/services/web/app/views/user/logout.pug
@@ -0,0 +1,24 @@
+extends ../layout-marketing
+
+block vars
+ - var suppressNavbar = true
+ - var suppressFooter = true
+ - var suppressPugCookieBanner = true
+ - metadata = {title: translate('log_out_of_overleaf')}
+
+block content
+ main#main-content.content.content-alt.full-height
+ .container.full-height
+ .row
+ .col-xl-6.offset-xl-3
+ .card
+ .card-body
+ .page-header
+ h1 #{translate("log_out_of_overleaf")}
+ form(name='logoutForm' data-ol-async-form action='/logout' method='POST')
+ input(name='_csrf' type='hidden' value=csrfToken)
+ p #{translate("log_out_of_overleaf_confirmation")}
+ .d-flex.justify-content-between.flex-row
+ button.btn-primary.btn(type='submit' data-ol-disabled-inflight)
+ span #{translate("log_out")}
+ a.btn-secondary.btn(href='/') #{translate("cancel")}
diff --git a/services/web/locales/en.json b/services/web/locales/en.json
index 0cb1390225..58a61522b7 100644
--- a/services/web/locales/en.json
+++ b/services/web/locales/en.json
@@ -1346,6 +1346,8 @@
"log_out": "Log Out",
"log_out_from": "Log out from __email__",
"log_out_lowercase_dot": "Log out.",
+ "log_out_of_overleaf": "Log out of __appName__",
+ "log_out_of_overleaf_confirmation": "Are you sure you want to log out of __appName__?",
"log_viewer_error": "There was a problem displaying this project’s compilation errors and logs.",
"logged_in_with_email": "You are currently logged in to __appName__ with the email __email__.",
"logging_in": "Logging in",