first commit

This commit is contained in:
2025-04-24 13:11:28 +08:00
commit ff9c54d5e4
5960 changed files with 834111 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
import Path from 'node:path'
import { fileURLToPath } from 'node:url'
import UserGetter from '../../../../app/src/Features/User/UserGetter.js'
import UserRegistrationHandler from '../../../../app/src/Features/User/UserRegistrationHandler.js'
import ErrorController from '../../../../app/src/Features/Errors/ErrorController.js'
import { expressify } from '@overleaf/promise-utils'
const __dirname = Path.dirname(fileURLToPath(import.meta.url))
function registerNewUser(req, res, next) {
res.render(Path.resolve(__dirname, '../views/user/register'))
}
async function register(req, res, next) {
const { email } = req.body
if (email == null || email === '') {
return res.sendStatus(422) // Unprocessable Entity
}
const { user, setNewPasswordUrl } =
await UserRegistrationHandler.promises.registerNewUserAndSendActivationEmail(
email
)
res.json({
email: user.email,
setNewPasswordUrl,
})
}
async function activateAccountPage(req, res, next) {
// An 'activation' is actually just a password reset on an account that
// was set with a random password originally.
if (req.query.user_id == null || req.query.token == null) {
return ErrorController.notFound(req, res)
}
if (typeof req.query.user_id !== 'string') {
return ErrorController.forbidden(req, res)
}
const user = await UserGetter.promises.getUser(req.query.user_id, {
email: 1,
loginCount: 1,
})
if (!user) {
return ErrorController.notFound(req, res)
}
if (user.loginCount > 0) {
// Already seen this user, so account must be activated.
// This lets users keep clicking the 'activate' link in their email
// as a way to log in which, if I know our users, they will.
return res.redirect(`/login`)
}
req.session.doLoginAfterPasswordReset = true
res.render(Path.resolve(__dirname, '../views/user/activate'), {
title: 'activate_account',
email: user.email,
token: req.query.token,
})
}
export default {
registerNewUser,
register: expressify(register),
activateAccountPage: expressify(activateAccountPage),
}

View File

@@ -0,0 +1,30 @@
import logger from '@overleaf/logger'
import UserActivateController from './UserActivateController.mjs'
import AuthenticationController from '../../../../app/src/Features/Authentication/AuthenticationController.js'
import AuthorizationMiddleware from '../../../../app/src/Features/Authorization/AuthorizationMiddleware.js'
export default {
apply(webRouter) {
logger.debug({}, 'Init UserActivate router')
webRouter.get(
'/admin/user',
AuthorizationMiddleware.ensureUserIsSiteAdmin,
(req, res) => res.redirect('/admin/register')
)
webRouter.get('/user/activate', UserActivateController.activateAccountPage)
AuthenticationController.addEndpointToLoginWhitelist('/user/activate')
webRouter.get(
'/admin/register',
AuthorizationMiddleware.ensureUserIsSiteAdmin,
UserActivateController.registerNewUser
)
webRouter.post(
'/admin/register',
AuthorizationMiddleware.ensureUserIsSiteAdmin,
UserActivateController.register
)
},
}

View File

@@ -0,0 +1 @@
{ "extends": "../../../../tsconfig.backend.json" }

View File

@@ -0,0 +1,72 @@
extends ../../../../../app/views/layout-website-redesign-bootstrap-5
block content
main.content.content-alt#main-content
.container
div.col-lg-6.col-xl-4.m-auto
.notification-list
.notification.notification-type-success(aria-live="off" role="alert")
.notification-content-and-cta
.notification-icon
span.material-symbols(aria-hidden="true")
| check_circle
.notification-content
p
| #{translate("nearly_activated")}
h1.h3 #{translate("please_set_a_password")}
form(
data-ol-async-form
name="activationForm",
action="/user/password/set",
method="POST",
)
+formMessages()
+customFormMessage('token-expired', 'danger')
| #{translate("activation_token_expired")}
+customFormMessage('invalid-password', 'danger')
| #{translate('invalid_password')}
input(name='_csrf', type='hidden', value=csrfToken)
input(
type="hidden",
name="passwordResetToken",
value=token
)
.form-group
label(for='emailField') #{translate("email")}
input.form-control#emailField(
aria-label="email",
type='email',
name='email',
placeholder="email@example.com",
autocomplete="username"
value=email
required,
disabled
)
.form-group
label(for='passwordField') #{translate("password")}
input.form-control#passwordField(
type='password',
name='password',
placeholder="********",
autocomplete="new-password",
autofocus,
required,
minlength=settings.passwordStrengthOptions.length.min
)
.actions
button.btn.btn-primary(
type='submit',
data-ol-disabled-inflight
aria-label=translate('activate')
)
span(data-ol-inflight="idle")
| #{translate('activate')}
span(hidden data-ol-inflight="pending")
| #{translate('activating')}…

View File

@@ -0,0 +1,13 @@
extends ../../../../../app/views/layout-marketing
block entrypointVar
- entrypoint = 'modules/user-activate/pages/user-activate-page'
block vars
- bootstrap5PageStatus = 'disabled'
block content
.content.content-alt#main-content
.container
#user-activate-register-container