first commit
This commit is contained in:
@@ -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),
|
||||
}
|
@@ -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
|
||||
)
|
||||
},
|
||||
}
|
1
services/web/modules/user-activate/app/src/tsconfig.json
Normal file
1
services/web/modules/user-activate/app/src/tsconfig.json
Normal file
@@ -0,0 +1 @@
|
||||
{ "extends": "../../../../tsconfig.backend.json" }
|
@@ -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')}…
|
@@ -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
|
@@ -0,0 +1,87 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import { postJSON } from '../../../../../frontend/js/infrastructure/fetch-json'
|
||||
|
||||
function RegisterForm({
|
||||
setRegistrationSuccess,
|
||||
setEmails,
|
||||
setRegisterError,
|
||||
setFailedEmails,
|
||||
}) {
|
||||
function handleRegister(event) {
|
||||
event.preventDefault()
|
||||
const formData = new FormData(event.target)
|
||||
const formDataAsEntries = formData.entries()
|
||||
const formDataAsObject = Object.fromEntries(formDataAsEntries)
|
||||
const emailString = formDataAsObject.email
|
||||
setRegistrationSuccess(false)
|
||||
setRegisterError(false)
|
||||
setEmails([])
|
||||
registerGivenUsers(parseEmails(emailString))
|
||||
}
|
||||
|
||||
async function registerGivenUsers(emails) {
|
||||
const registeredEmails = []
|
||||
const failingEmails = []
|
||||
for (const email of emails) {
|
||||
try {
|
||||
const result = await registerUser(email)
|
||||
registeredEmails.push(result)
|
||||
} catch {
|
||||
failingEmails.push(email)
|
||||
}
|
||||
}
|
||||
if (registeredEmails.length > 0) setRegistrationSuccess(true)
|
||||
if (failingEmails.length > 0) {
|
||||
setRegisterError(true)
|
||||
setFailedEmails(failingEmails)
|
||||
}
|
||||
setEmails(registeredEmails)
|
||||
}
|
||||
|
||||
function registerUser(email) {
|
||||
const options = { email }
|
||||
const url = `/admin/register`
|
||||
return postJSON(url, { body: options })
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleRegister}>
|
||||
<div className="row">
|
||||
<div className="col-md-4 col-xs-8">
|
||||
<input
|
||||
className="form-control"
|
||||
name="email"
|
||||
type="text"
|
||||
placeholder="jane@example.com, joe@example.com"
|
||||
aria-label="emails to register"
|
||||
aria-describedby="input-details"
|
||||
/>
|
||||
<p id="input-details" className="sr-only">
|
||||
Enter the emails you would like to register and separate them using
|
||||
commas
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-md-8 col-xs-4">
|
||||
<button className="btn btn-primary">Register</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
function parseEmails(emailsText) {
|
||||
const regexBySpaceOrComma = /[\s,]+/
|
||||
let emails = emailsText.split(regexBySpaceOrComma)
|
||||
emails.map(email => email.trim())
|
||||
emails = emails.filter(email => email.indexOf('@') !== -1)
|
||||
return emails
|
||||
}
|
||||
|
||||
RegisterForm.propTypes = {
|
||||
setRegistrationSuccess: PropTypes.func,
|
||||
setEmails: PropTypes.func,
|
||||
setRegisterError: PropTypes.func,
|
||||
setFailedEmails: PropTypes.func,
|
||||
}
|
||||
|
||||
export default RegisterForm
|
@@ -0,0 +1,92 @@
|
||||
import { useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import RegisterForm from './register-form'
|
||||
function UserActivateRegister() {
|
||||
const [emails, setEmails] = useState([])
|
||||
const [failedEmails, setFailedEmails] = useState([])
|
||||
const [registerError, setRegisterError] = useState(false)
|
||||
const [registrationSuccess, setRegistrationSuccess] = useState(false)
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-md-12">
|
||||
<div className="card">
|
||||
<div className="page-header">
|
||||
<h1> Register New Users</h1>
|
||||
</div>
|
||||
<RegisterForm
|
||||
setRegistrationSuccess={setRegistrationSuccess}
|
||||
setEmails={setEmails}
|
||||
setRegisterError={setRegisterError}
|
||||
setFailedEmails={setFailedEmails}
|
||||
/>
|
||||
{registerError ? (
|
||||
<UserActivateError failedEmails={failedEmails} />
|
||||
) : null}
|
||||
{registrationSuccess ? (
|
||||
<>
|
||||
<SuccessfulRegistrationMessage />
|
||||
<hr />
|
||||
<DisplayEmailsList emails={emails} />
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function UserActivateError({ failedEmails }) {
|
||||
return (
|
||||
<div className="row-spaced text-danger">
|
||||
<p>Sorry, an error occured, failed to register these emails.</p>
|
||||
{failedEmails.map(email => (
|
||||
<p key={email}>{email}</p>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function SuccessfulRegistrationMessage() {
|
||||
return (
|
||||
<div className="row-spaced text-success">
|
||||
<p>We've sent out welcome emails to the registered users.</p>
|
||||
<p>
|
||||
You can also manually send them URLs below to allow them to reset their
|
||||
password and log in for the first time.
|
||||
</p>
|
||||
<p>
|
||||
(Password reset tokens will expire after one week and the user will need
|
||||
registering again).
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function DisplayEmailsList({ emails }) {
|
||||
return (
|
||||
<table className="table table-striped ">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Email</th>
|
||||
<th>Set Password Url</th>
|
||||
</tr>
|
||||
{emails.map(user => (
|
||||
<tr key={user.email}>
|
||||
<td>{user.email}</td>
|
||||
<td style={{ wordBreak: 'break-all' }}>{user.setNewPasswordUrl}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
}
|
||||
|
||||
DisplayEmailsList.propTypes = {
|
||||
emails: PropTypes.array,
|
||||
}
|
||||
UserActivateError.propTypes = {
|
||||
failedEmails: PropTypes.array,
|
||||
}
|
||||
|
||||
export default UserActivateRegister
|
@@ -0,0 +1,9 @@
|
||||
import '@/marketing'
|
||||
|
||||
import ReactDOM from 'react-dom'
|
||||
import UserActivateRegister from '../components/user-activate-register'
|
||||
|
||||
ReactDOM.render(
|
||||
<UserActivateRegister />,
|
||||
document.getElementById('user-activate-register-container')
|
||||
)
|
12
services/web/modules/user-activate/index.mjs
Normal file
12
services/web/modules/user-activate/index.mjs
Normal file
@@ -0,0 +1,12 @@
|
||||
import UserActivateRouter from './app/src/UserActivateRouter.mjs'
|
||||
|
||||
/**
|
||||
* @import { WebModule } from "../../types/web-module"
|
||||
*/
|
||||
|
||||
/** @type {WebModule} */
|
||||
const UserActivateModule = {
|
||||
router: UserActivateRouter,
|
||||
}
|
||||
|
||||
export default UserActivateModule
|
@@ -0,0 +1,62 @@
|
||||
import { expect } from 'chai'
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import sinon from 'sinon'
|
||||
import RegisterForm from '../../../../frontend/js/components/register-form'
|
||||
|
||||
describe('RegisterForm', function () {
|
||||
beforeEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
it('should render the register form', async function () {
|
||||
const setRegistrationSuccessStub = sinon.stub()
|
||||
const setEmailsStub = sinon.stub()
|
||||
const setRegisterErrorStub = sinon.stub()
|
||||
const setFailedEmailsStub = sinon.stub()
|
||||
|
||||
render(
|
||||
<RegisterForm
|
||||
setRegistrationSuccess={setRegistrationSuccessStub}
|
||||
setEmails={setEmailsStub}
|
||||
setRegisterError={setRegisterErrorStub}
|
||||
setFailedEmails={setFailedEmailsStub}
|
||||
/>
|
||||
)
|
||||
await screen.findByLabelText('emails to register')
|
||||
screen.getByRole('button', { name: /register/i })
|
||||
})
|
||||
|
||||
it('should call the fetch request when register button is pressed', async function () {
|
||||
const email = 'abc@gmail.com'
|
||||
const setRegistrationSuccessStub = sinon.stub()
|
||||
const setEmailsStub = sinon.stub()
|
||||
const setRegisterErrorStub = sinon.stub()
|
||||
const setFailedEmailsStub = sinon.stub()
|
||||
|
||||
const endPointResponse = {
|
||||
status: 200,
|
||||
body: {
|
||||
email,
|
||||
setNewPasswordUrl: 'SetNewPasswordURL',
|
||||
},
|
||||
}
|
||||
const registerMock = fetchMock.post('/admin/register', endPointResponse)
|
||||
|
||||
render(
|
||||
<RegisterForm
|
||||
setRegistrationSuccess={setRegistrationSuccessStub}
|
||||
setEmails={setEmailsStub}
|
||||
setRegisterError={setRegisterErrorStub}
|
||||
setFailedEmails={setFailedEmailsStub}
|
||||
/>
|
||||
)
|
||||
const registerInput = screen.getByLabelText('emails to register')
|
||||
const registerButton = screen.getByRole('button', { name: /register/i })
|
||||
fireEvent.change(registerInput, { target: { value: email } })
|
||||
fireEvent.click(registerButton)
|
||||
expect(registerMock.callHistory.called()).to.be.true
|
||||
})
|
||||
})
|
@@ -0,0 +1,140 @@
|
||||
import { expect } from 'chai'
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import UserActivateRegister from '../../../../frontend/js/components/user-activate-register'
|
||||
|
||||
describe('UserActivateRegister', function () {
|
||||
beforeEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
it('should display the error message', async function () {
|
||||
const email = 'abc@gmail.com'
|
||||
render(<UserActivateRegister />)
|
||||
const endPointResponse = {
|
||||
status: 500,
|
||||
}
|
||||
const registerMock = fetchMock.post('/admin/register', endPointResponse)
|
||||
const registerInput = screen.getByLabelText('emails to register')
|
||||
const registerButton = screen.getByRole('button', { name: /register/i })
|
||||
|
||||
fireEvent.change(registerInput, { target: { value: email } })
|
||||
fireEvent.click(registerButton)
|
||||
|
||||
expect(registerMock.callHistory.called()).to.be.true
|
||||
await screen.findByText('Sorry, an error occured', { exact: false })
|
||||
})
|
||||
|
||||
it('should display the success message', async function () {
|
||||
const email = 'abc@gmail.com'
|
||||
render(<UserActivateRegister />)
|
||||
const endPointResponse = {
|
||||
status: 200,
|
||||
body: {
|
||||
email,
|
||||
setNewPasswordUrl: 'SetNewPasswordURL',
|
||||
},
|
||||
}
|
||||
const registerMock = fetchMock.post('/admin/register', endPointResponse)
|
||||
const registerInput = screen.getByLabelText('emails to register')
|
||||
const registerButton = screen.getByRole('button', { name: /register/i })
|
||||
|
||||
fireEvent.change(registerInput, { target: { value: email } })
|
||||
fireEvent.click(registerButton)
|
||||
|
||||
expect(registerMock.callHistory.called()).to.be.true
|
||||
await screen.findByText(
|
||||
"We've sent out welcome emails to the registered users."
|
||||
)
|
||||
})
|
||||
|
||||
it('should display the registered emails', async function () {
|
||||
const email = 'abc@gmail.com, def@gmail.com'
|
||||
render(<UserActivateRegister />)
|
||||
const endPointResponse1 = {
|
||||
status: 200,
|
||||
body: {
|
||||
email: 'abc@gmail.com',
|
||||
setNewPasswordUrl: 'SetNewPasswordURL',
|
||||
},
|
||||
}
|
||||
const endPointResponse2 = {
|
||||
status: 200,
|
||||
body: {
|
||||
email: 'def@gmail.com',
|
||||
setNewPasswordUrl: 'SetNewPasswordURL',
|
||||
},
|
||||
}
|
||||
const registerMock = fetchMock.post('/admin/register', (path, req) => {
|
||||
const body = JSON.parse(req.body)
|
||||
if (body.email === 'abc@gmail.com') return endPointResponse1
|
||||
else if (body.email === 'def@gmail.com') return endPointResponse2
|
||||
})
|
||||
const registerInput = screen.getByLabelText('emails to register')
|
||||
const registerButton = screen.getByRole('button', { name: /register/i })
|
||||
|
||||
fireEvent.change(registerInput, { target: { value: email } })
|
||||
fireEvent.click(registerButton)
|
||||
|
||||
expect(registerMock.callHistory.called()).to.be.true
|
||||
await screen.findByText('abc@gmail.com')
|
||||
await screen.findByText('def@gmail.com')
|
||||
})
|
||||
|
||||
it('should display the failed emails', async function () {
|
||||
const email = 'abc@, def@'
|
||||
render(<UserActivateRegister />)
|
||||
const endPointResponse1 = {
|
||||
status: 500,
|
||||
}
|
||||
const endPointResponse2 = {
|
||||
status: 500,
|
||||
}
|
||||
const registerMock = fetchMock.post('/admin/register', (path, req) => {
|
||||
const body = JSON.parse(req.body)
|
||||
if (body.email === 'abc@') return endPointResponse1
|
||||
else if (body.email === 'def@') return endPointResponse2
|
||||
})
|
||||
const registerInput = screen.getByLabelText('emails to register')
|
||||
const registerButton = screen.getByRole('button', { name: /register/i })
|
||||
|
||||
fireEvent.change(registerInput, { target: { value: email } })
|
||||
fireEvent.click(registerButton)
|
||||
|
||||
expect(registerMock.callHistory.called()).to.be.true
|
||||
await screen.findByText('abc@')
|
||||
await screen.findByText('def@')
|
||||
})
|
||||
|
||||
it('should display the registered and failed emails together', async function () {
|
||||
const email = 'abc@gmail.com, def@'
|
||||
render(<UserActivateRegister />)
|
||||
const endPointResponse1 = {
|
||||
status: 200,
|
||||
body: {
|
||||
email: 'abc@gmail.com',
|
||||
setNewPasswordUrl: 'SetNewPasswordURL',
|
||||
},
|
||||
}
|
||||
const endPointResponse2 = {
|
||||
status: 500,
|
||||
}
|
||||
const registerMock = fetchMock.post('/admin/register', (path, req) => {
|
||||
const body = JSON.parse(req.body)
|
||||
if (body.email === 'abc@gmail.com') return endPointResponse1
|
||||
else if (body.email === 'def@gmail.com') return endPointResponse2
|
||||
else return 500
|
||||
})
|
||||
const registerInput = screen.getByLabelText('emails to register')
|
||||
const registerButton = screen.getByRole('button', { name: /register/i })
|
||||
|
||||
fireEvent.change(registerInput, { target: { value: email } })
|
||||
fireEvent.click(registerButton)
|
||||
|
||||
expect(registerMock.callHistory.called()).to.be.true
|
||||
await screen.findByText('abc@gmail.com')
|
||||
await screen.findByText('def@')
|
||||
})
|
||||
})
|
@@ -0,0 +1,134 @@
|
||||
import Path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { strict as esmock } from 'esmock'
|
||||
import sinon from 'sinon'
|
||||
|
||||
const __dirname = Path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
const MODULE_PATH = '../../../app/src/UserActivateController.mjs'
|
||||
|
||||
const VIEW_PATH = Path.join(__dirname, '../../../app/views/user/activate')
|
||||
|
||||
describe('UserActivateController', function () {
|
||||
beforeEach(async function () {
|
||||
this.user = {
|
||||
_id: (this.user_id = 'kwjewkl'),
|
||||
features: {},
|
||||
email: 'joe@example.com',
|
||||
}
|
||||
|
||||
this.UserGetter = {
|
||||
promises: {
|
||||
getUser: sinon.stub(),
|
||||
},
|
||||
}
|
||||
this.UserRegistrationHandler = { promises: {} }
|
||||
this.ErrorController = { notFound: sinon.stub() }
|
||||
this.SplitTestHandler = {
|
||||
promises: {
|
||||
getAssignment: sinon.stub().resolves({ variant: 'default' }),
|
||||
},
|
||||
}
|
||||
this.UserActivateController = await esmock(MODULE_PATH, {
|
||||
'../../../../../app/src/Features/User/UserGetter.js': this.UserGetter,
|
||||
'../../../../../app/src/Features/User/UserRegistrationHandler.js':
|
||||
this.UserRegistrationHandler,
|
||||
'../../../../../app/src/Features/Errors/ErrorController.js':
|
||||
this.ErrorController,
|
||||
'../../../../../app/src/Features/SplitTests/SplitTestHandler':
|
||||
this.SplitTestHandler,
|
||||
})
|
||||
this.req = {
|
||||
body: {},
|
||||
query: {},
|
||||
session: {
|
||||
user: this.user,
|
||||
},
|
||||
}
|
||||
this.res = {
|
||||
json: sinon.stub(),
|
||||
}
|
||||
})
|
||||
|
||||
describe('activateAccountPage', function () {
|
||||
beforeEach(function () {
|
||||
this.UserGetter.promises.getUser = sinon.stub().resolves(this.user)
|
||||
this.req.query.user_id = this.user_id
|
||||
this.req.query.token = this.token = 'mock-token-123'
|
||||
})
|
||||
|
||||
it('should 404 without a user_id', async function (done) {
|
||||
delete this.req.query.user_id
|
||||
this.ErrorController.notFound = () => done()
|
||||
this.UserActivateController.activateAccountPage(this.req, this.res)
|
||||
})
|
||||
|
||||
it('should 404 without a token', function (done) {
|
||||
delete this.req.query.token
|
||||
this.ErrorController.notFound = () => done()
|
||||
this.UserActivateController.activateAccountPage(this.req, this.res)
|
||||
})
|
||||
|
||||
it('should 404 without a valid user_id', function (done) {
|
||||
this.UserGetter.promises.getUser = sinon.stub().resolves(null)
|
||||
this.ErrorController.notFound = () => done()
|
||||
this.UserActivateController.activateAccountPage(this.req, this.res)
|
||||
})
|
||||
|
||||
it('should 403 for complex user_id', function (done) {
|
||||
this.ErrorController.forbidden = () => done()
|
||||
this.req.query.user_id = { first_name: 'X' }
|
||||
this.UserActivateController.activateAccountPage(this.req, this.res)
|
||||
})
|
||||
|
||||
it('should redirect activated users to login', function (done) {
|
||||
this.user.loginCount = 1
|
||||
this.res.redirect = url => {
|
||||
sinon.assert.calledWith(this.UserGetter.promises.getUser, this.user_id)
|
||||
url.should.equal('/login')
|
||||
return done()
|
||||
}
|
||||
this.UserActivateController.activateAccountPage(this.req, this.res)
|
||||
})
|
||||
|
||||
it('render the activation page if the user has not logged in before', function (done) {
|
||||
this.user.loginCount = 0
|
||||
this.res.render = (page, opts) => {
|
||||
page.should.equal(VIEW_PATH)
|
||||
opts.email.should.equal(this.user.email)
|
||||
opts.token.should.equal(this.token)
|
||||
return done()
|
||||
}
|
||||
this.UserActivateController.activateAccountPage(this.req, this.res)
|
||||
})
|
||||
})
|
||||
|
||||
describe('register', function () {
|
||||
beforeEach(async function () {
|
||||
this.UserRegistrationHandler.promises.registerNewUserAndSendActivationEmail =
|
||||
sinon.stub().resolves({
|
||||
user: this.user,
|
||||
setNewPasswordUrl: (this.url = 'mock/url'),
|
||||
})
|
||||
this.req.body.email = this.user.email = this.email = 'email@example.com'
|
||||
await this.UserActivateController.register(this.req, this.res)
|
||||
})
|
||||
|
||||
it('should register the user and send them an email', function () {
|
||||
sinon.assert.calledWith(
|
||||
this.UserRegistrationHandler.promises
|
||||
.registerNewUserAndSendActivationEmail,
|
||||
this.email
|
||||
)
|
||||
})
|
||||
|
||||
it('should return the user and activation url', function () {
|
||||
this.res.json
|
||||
.calledWith({
|
||||
email: this.email,
|
||||
setNewPasswordUrl: this.url,
|
||||
})
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
@@ -0,0 +1 @@
|
||||
{ "extends": "../../../../tsconfig.backend.json" }
|
Reference in New Issue
Block a user