mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-04 01:18:20 +02:00
Feat/add new user email (#793)
* feat: send email when adding a new user * fix: rename method * fix: create welcome email * fix: update email templates * fix: add name to templates * refactor: reduce database calls to one * fix: alter tests * fix: remove console logs
This commit is contained in:
parent
b0e6d8c363
commit
c58612fc8f
@ -81,6 +81,14 @@ export class ResetTokenStore {
|
||||
return rowToResetToken(row);
|
||||
}
|
||||
|
||||
async getActiveTokens(): Promise<IResetToken[]> {
|
||||
const rows = await this.db<IResetTokenTable>(TABLE)
|
||||
.whereNull('used_at')
|
||||
.andWhere('expires_at', '>', new Date());
|
||||
|
||||
return rows.map(rowToResetToken);
|
||||
}
|
||||
|
||||
async insert(newToken: IResetTokenCreate): Promise<IResetToken> {
|
||||
const [row] = await this.db<IResetTokenTable>(TABLE)
|
||||
.insert(newToken)
|
||||
|
@ -5,6 +5,8 @@ import { AccessService } from '../../services/access-service';
|
||||
import { Logger } from '../../logger';
|
||||
import { handleErrors } from './util';
|
||||
import { IUnleashConfig } from '../../types/option';
|
||||
import { EmailService, MAIL_ACCEPTED } from '../../services/email-service';
|
||||
import ResetTokenService from '../../services/reset-token-service';
|
||||
|
||||
const getCreatorUsernameOrPassword = req => req.user.username || req.user.email;
|
||||
|
||||
@ -15,11 +17,20 @@ export default class UserAdminController extends Controller {
|
||||
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(config: IUnleashConfig, { userService, accessService }) {
|
||||
private emailService: EmailService;
|
||||
|
||||
private resetTokenService: ResetTokenService;
|
||||
|
||||
constructor(
|
||||
config: IUnleashConfig,
|
||||
{ userService, accessService, emailService, resetTokenService },
|
||||
) {
|
||||
super(config);
|
||||
this.userService = userService;
|
||||
this.accessService = accessService;
|
||||
this.logger = config.getLogger('routes/user-controller.ts');
|
||||
this.emailService = emailService;
|
||||
this.resetTokenService = resetTokenService;
|
||||
|
||||
this.get('/', this.getUsers, ADMIN);
|
||||
this.get('/search', this.search);
|
||||
@ -51,8 +62,14 @@ export default class UserAdminController extends Controller {
|
||||
try {
|
||||
const users = await this.userService.getAll();
|
||||
const rootRoles = await this.accessService.getRootRoles();
|
||||
const inviteLinks = await this.resetTokenService.getActiveInvitations();
|
||||
|
||||
res.json({ users, rootRoles });
|
||||
const usersWithInviteLinks = users.map(user => {
|
||||
const inviteLink = inviteLinks[user.id] || '';
|
||||
return { ...user, inviteLink };
|
||||
});
|
||||
|
||||
res.json({ users: usersWithInviteLinks, rootRoles });
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
res.status(500).send({ msg: 'server errors' });
|
||||
@ -75,15 +92,44 @@ export default class UserAdminController extends Controller {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async createUser(req, res): Promise<void> {
|
||||
const { username, email, name, rootRole } = req.body;
|
||||
const { user } = req;
|
||||
|
||||
try {
|
||||
const user = await this.userService.createUser({
|
||||
const createdUser = await this.userService.createUser({
|
||||
username,
|
||||
email,
|
||||
name,
|
||||
rootRole: Number(rootRole),
|
||||
});
|
||||
res.status(201).send({ ...user, rootRole });
|
||||
|
||||
const inviteLink = await this.resetTokenService.createNewUserUrl(
|
||||
createdUser.id,
|
||||
user.email,
|
||||
);
|
||||
|
||||
const emailConfigured = this.emailService.configured();
|
||||
let sentMetaData = null;
|
||||
if (emailConfigured) {
|
||||
sentMetaData = await this.emailService.sendGettingStartedMail(
|
||||
createdUser.name,
|
||||
createdUser.email,
|
||||
inviteLink.toString(),
|
||||
);
|
||||
} else {
|
||||
this.logger.warn(
|
||||
'email was not sent to the user because email configuration is lacking',
|
||||
);
|
||||
}
|
||||
|
||||
const emailSent =
|
||||
sentMetaData?.response.includes(MAIL_ACCEPTED) || false;
|
||||
|
||||
res.status(201).send({
|
||||
...createdUser,
|
||||
inviteLink,
|
||||
emailSent,
|
||||
rootRole,
|
||||
});
|
||||
} catch (e) {
|
||||
this.logger.warn(e.message);
|
||||
res.status(400).send([{ msg: e.message }]);
|
||||
|
@ -33,6 +33,8 @@ export interface IEmailOptions {
|
||||
const RESET_MAIL_SUBJECT = 'Unleash - Reset your password';
|
||||
const GETTING_STARTED_SUBJECT = 'Welcome to Unleash';
|
||||
|
||||
export const MAIL_ACCEPTED = '250 Accepted';
|
||||
|
||||
export class EmailService {
|
||||
private logger: Logger;
|
||||
|
||||
@ -66,7 +68,7 @@ export class EmailService {
|
||||
recipient: string,
|
||||
resetLink: string,
|
||||
): Promise<SentMessageInfo> {
|
||||
if (this.mailer !== undefined) {
|
||||
if (this.configured()) {
|
||||
const year = new Date().getFullYear();
|
||||
const bodyHtml = await this.compileTemplate(
|
||||
'reset-password',
|
||||
@ -108,7 +110,7 @@ export class EmailService {
|
||||
recipient: string,
|
||||
passwordLink: string,
|
||||
): Promise<SentMessageInfo> {
|
||||
if (this.mailer !== undefined) {
|
||||
if (this.configured()) {
|
||||
const year = new Date().getFullYear();
|
||||
const context = { passwordLink, name, year };
|
||||
const bodyHtml = await this.compileTemplate(
|
||||
@ -174,4 +176,11 @@ export class EmailService {
|
||||
}
|
||||
throw new NotFoundError('Could not find template');
|
||||
}
|
||||
|
||||
configured(): boolean {
|
||||
if (this.sender !== 'not-configured' && this.mailer !== undefined) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,10 @@ interface IStores {
|
||||
userStore: UserStore;
|
||||
}
|
||||
|
||||
interface IInviteLinks {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export default class ResetTokenService {
|
||||
private store: ResetTokenStore;
|
||||
|
||||
@ -45,6 +49,25 @@ export default class ResetTokenService {
|
||||
}
|
||||
}
|
||||
|
||||
async getActiveInvitations(): Promise<IInviteLinks> {
|
||||
try {
|
||||
const tokens = await this.store.getActiveTokens();
|
||||
const links = tokens.reduce((acc, token) => {
|
||||
const inviteLink = this.getExistingInvitationUrl(
|
||||
token,
|
||||
).toString();
|
||||
|
||||
acc[token.userId] = inviteLink;
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return links;
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
async isValid(token: string): Promise<IResetToken> {
|
||||
let t;
|
||||
try {
|
||||
@ -58,6 +81,10 @@ export default class ResetTokenService {
|
||||
throw new UsedTokenError(t.usedAt);
|
||||
}
|
||||
|
||||
private getExistingInvitationUrl(token: IResetToken) {
|
||||
return new URL(`/#/new-user?token=${token.token}`, this.unleashBase);
|
||||
}
|
||||
|
||||
private async createResetUrl(
|
||||
forUser: number,
|
||||
creator: string,
|
||||
@ -69,11 +96,6 @@ export default class ResetTokenService {
|
||||
);
|
||||
}
|
||||
|
||||
async createWelcomeUrl(forUser: number, creator: string): Promise<URL> {
|
||||
const path = '/#/new-user';
|
||||
return this.createResetUrl(forUser, creator, path);
|
||||
}
|
||||
|
||||
async createResetPasswordUrl(
|
||||
forUser: number,
|
||||
creator: string,
|
||||
@ -82,6 +104,11 @@ export default class ResetTokenService {
|
||||
return this.createResetUrl(forUser, creator, path);
|
||||
}
|
||||
|
||||
async createNewUserUrl(forUser: number, creator: string): Promise<URL> {
|
||||
const path = '/#/new-user';
|
||||
return this.createResetUrl(forUser, creator, path);
|
||||
}
|
||||
|
||||
async createToken(
|
||||
tokenUser: number,
|
||||
creator: string,
|
||||
|
@ -17,6 +17,7 @@ export interface IUser {
|
||||
name?: string;
|
||||
username?: string;
|
||||
email?: string;
|
||||
inviteLink?: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
|
@ -1,21 +1,540 @@
|
||||
<!DOCTYPE html>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>Welcome to Unleash - {{ name }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
Welcome to Unleash
|
||||
</header>
|
||||
<section>
|
||||
First step: Setup your password by visiting <a href="{{ passwordLink }}" title="Set Password link">{{ passwordLink }}</a>
|
||||
Second step: Visit your instance at {{ unleashUrl }}
|
||||
</section>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>*|MC:SUBJECT|*</title>
|
||||
<style type="text/css">
|
||||
/* /\/\/\/\/\/\/\/\/ CLIENT-SPECIFIC STYLES /\/\/\/\/\/\/\/\/ */
|
||||
#outlook a{padding:0;} /* Force Outlook to provide a "view in browser" message */
|
||||
.ReadMsgBody{width:100%;} .ExternalClass{width:100%;} /* Force Hotmail to display emails at full width */
|
||||
.ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div {line-height: 100%;} /* Force Hotmail to display normal line spacing */
|
||||
body, table, td, p, a, li, blockquote{-webkit-text-size-adjust:100%; -ms-text-size-adjust:100%;} /* Prevent WebKit and Windows mobile changing default text sizes */
|
||||
table, td{mso-table-lspace:0pt; mso-table-rspace:0pt;} /* Remove spacing between tables in Outlook 2007 and up */
|
||||
img{-ms-interpolation-mode:bicubic;} /* Allow smoother rendering of resized image in Internet Explorer */
|
||||
|
||||
<footer>
|
||||
© {{ year }} - Unleash
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
/* /\/\/\/\/\/\/\/\/ RESET STYLES /\/\/\/\/\/\/\/\/ */
|
||||
body{margin:0; padding:0;}
|
||||
img{border:0; height:auto; line-height:100%; outline:none; text-decoration:none;}
|
||||
table{border-collapse:collapse !important;}
|
||||
body, #bodyTable, #bodyCell{height:100% !important; margin:0; padding:0; width:100% !important;}
|
||||
|
||||
/* /\/\/\/\/\/\/\/\/ TEMPLATE STYLES /\/\/\/\/\/\/\/\/ */
|
||||
|
||||
/* ========== Page Styles ========== */
|
||||
|
||||
#bodyCell{padding:20px;}
|
||||
#templateContainer{width:600px;}
|
||||
|
||||
/**
|
||||
* @tab Page
|
||||
* @section background style
|
||||
* @tip Set the background color and top border for your email. You may want to choose colors that match your company's branding.
|
||||
* @theme page
|
||||
*/
|
||||
body, #bodyTable{
|
||||
/*@editable*/ background-color:#DEE0E2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @tab Page
|
||||
* @section background style
|
||||
* @tip Set the background color and top border for your email. You may want to choose colors that match your company's branding.
|
||||
* @theme page
|
||||
*/
|
||||
#bodyCell{
|
||||
/*@editable*/ border-top:4px solid #BBBBBB;
|
||||
}
|
||||
|
||||
/**
|
||||
* @tab Page
|
||||
* @section email border
|
||||
* @tip Set the border for your email.
|
||||
*/
|
||||
#templateContainer{
|
||||
/*@editable*/ border:1px solid #BBBBBB;
|
||||
}
|
||||
|
||||
/**
|
||||
* @tab Page
|
||||
* @section heading 1
|
||||
* @tip Set the styling for all first-level headings in your emails. These should be the largest of your headings.
|
||||
* @style heading 1
|
||||
*/
|
||||
h1{
|
||||
/*@editable*/ color:#202020 !important;
|
||||
display:block;
|
||||
/*@editable*/ font-family:Helvetica;
|
||||
/*@editable*/ font-size:26px;
|
||||
/*@editable*/ font-style:normal;
|
||||
/*@editable*/ font-weight:bold;
|
||||
/*@editable*/ line-height:100%;
|
||||
/*@editable*/ letter-spacing:normal;
|
||||
margin-top:0;
|
||||
margin-right:0;
|
||||
margin-bottom:10px;
|
||||
margin-left:0;
|
||||
/*@editable*/ text-align:left;
|
||||
}
|
||||
|
||||
/**
|
||||
* @tab Page
|
||||
* @section heading 2
|
||||
* @tip Set the styling for all second-level headings in your emails.
|
||||
* @style heading 2
|
||||
*/
|
||||
h2{
|
||||
/*@editable*/ color:#404040 !important;
|
||||
display:block;
|
||||
/*@editable*/ font-family:Helvetica;
|
||||
/*@editable*/ font-size:20px;
|
||||
/*@editable*/ font-style:normal;
|
||||
/*@editable*/ font-weight:bold;
|
||||
/*@editable*/ line-height:100%;
|
||||
/*@editable*/ letter-spacing:normal;
|
||||
margin-top:0;
|
||||
margin-right:0;
|
||||
margin-bottom:10px;
|
||||
margin-left:0;
|
||||
/*@editable*/ text-align:left;
|
||||
}
|
||||
|
||||
/**
|
||||
* @tab Page
|
||||
* @section heading 3
|
||||
* @tip Set the styling for all third-level headings in your emails.
|
||||
* @style heading 3
|
||||
*/
|
||||
h3{
|
||||
/*@editable*/ color:#606060 !important;
|
||||
display:block;
|
||||
/*@editable*/ font-family:Helvetica;
|
||||
/*@editable*/ font-size:16px;
|
||||
/*@editable*/ font-style:italic;
|
||||
/*@editable*/ font-weight:normal;
|
||||
/*@editable*/ line-height:100%;
|
||||
/*@editable*/ letter-spacing:normal;
|
||||
margin-top:0;
|
||||
margin-right:0;
|
||||
margin-bottom:10px;
|
||||
margin-left:0;
|
||||
/*@editable*/ text-align:left;
|
||||
}
|
||||
|
||||
/**
|
||||
* @tab Page
|
||||
* @section heading 4
|
||||
* @tip Set the styling for all fourth-level headings in your emails. These should be the smallest of your headings.
|
||||
* @style heading 4
|
||||
*/
|
||||
h4{
|
||||
/*@editable*/ color:#808080 !important;
|
||||
display:block;
|
||||
/*@editable*/ font-family:Helvetica;
|
||||
/*@editable*/ font-size:14px;
|
||||
/*@editable*/ font-style:italic;
|
||||
/*@editable*/ font-weight:normal;
|
||||
/*@editable*/ line-height:100%;
|
||||
/*@editable*/ letter-spacing:normal;
|
||||
margin-top:0;
|
||||
margin-right:0;
|
||||
margin-bottom:10px;
|
||||
margin-left:0;
|
||||
/*@editable*/ text-align:left;
|
||||
}
|
||||
|
||||
/* ========== Header Styles ========== */
|
||||
|
||||
/**
|
||||
* @tab Header
|
||||
* @section preheader style
|
||||
* @tip Set the background color and bottom border for your email's preheader area.
|
||||
* @theme header
|
||||
*/
|
||||
#templatePreheader{
|
||||
/*@editable*/ background-color:#fff;
|
||||
/*@editable*/ border-bottom:1px solid #CCCCCC;
|
||||
}
|
||||
|
||||
/**
|
||||
* @tab Header
|
||||
* @section preheader text
|
||||
* @tip Set the styling for your email's preheader text. Choose a size and color that is easy to read.
|
||||
*/
|
||||
.preheaderContent{
|
||||
/*@editable*/ color:#808080;
|
||||
/*@editable*/ font-family:Helvetica;
|
||||
/*@editable*/ font-size:10px;
|
||||
/*@editable*/ line-height:125%;
|
||||
/*@editable*/ text-align:left;
|
||||
}
|
||||
|
||||
/**
|
||||
* @tab Header
|
||||
* @section preheader link
|
||||
* @tip Set the styling for your email's preheader links. Choose a color that helps them stand out from your text.
|
||||
*/
|
||||
.preheaderContent a:link, .preheaderContent a:visited, /* Yahoo! Mail Override */ .preheaderContent a .yshortcuts /* Yahoo! Mail Override */{
|
||||
/*@editable*/ color:#fff;
|
||||
/*@editable*/ font-weight:normal;
|
||||
/*@editable*/ text-decoration:underline;
|
||||
}
|
||||
|
||||
/**
|
||||
* @tab Header
|
||||
* @section header style
|
||||
* @tip Set the background color and borders for your email's header area.
|
||||
* @theme header
|
||||
*/
|
||||
#templateHeader{
|
||||
/*@editable*/ background-color:#fff;
|
||||
/*@editable*/ border-top:1px solid #FFFFFF;
|
||||
/*@editable*/ border-bottom:1px solid #CCCCCC;
|
||||
}
|
||||
|
||||
/**
|
||||
* @tab Header
|
||||
* @section header text
|
||||
* @tip Set the styling for your email's header text. Choose a size and color that is easy to read.
|
||||
*/
|
||||
.headerContent{
|
||||
/*@editable*/ color:#505050;
|
||||
/*@editable*/ font-family:Helvetica;
|
||||
/*@editable*/ font-size:20px;
|
||||
/*@editable*/ font-weight:bold;
|
||||
/*@editable*/ line-height:100%;
|
||||
/*@editable*/ padding-top:0;
|
||||
/*@editable*/ padding-right:0;
|
||||
/*@editable*/ padding-bottom:0;
|
||||
/*@editable*/ padding-left:0;
|
||||
/*@editable*/ text-align:left;
|
||||
/*@editable*/ vertical-align:middle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @tab Header
|
||||
* @section header link
|
||||
* @tip Set the styling for your email's header links. Choose a color that helps them stand out from your text.
|
||||
*/
|
||||
.headerContent a:link, .headerContent a:visited, /* Yahoo! Mail Override */ .headerContent a .yshortcuts /* Yahoo! Mail Override */{
|
||||
/*@editable*/ color:#fff;
|
||||
/*@editable*/ font-weight:normal;
|
||||
/*@editable*/ text-decoration:underline;
|
||||
}
|
||||
|
||||
#headerImage{
|
||||
height:auto;
|
||||
max-width:600px;
|
||||
}
|
||||
|
||||
/* ========== Body Styles ========== */
|
||||
|
||||
/**
|
||||
* @tab Body
|
||||
* @section body style
|
||||
* @tip Set the background color and borders for your email's body area.
|
||||
*/
|
||||
#templateBody{
|
||||
/*@editable*/ background-color:#fff;
|
||||
}
|
||||
|
||||
/**
|
||||
* @tab Body
|
||||
* @section body text
|
||||
* @tip Set the styling for your email's main content text. Choose a size and color that is easy to read.
|
||||
* @theme main
|
||||
*/
|
||||
.bodyContent{
|
||||
/*@editable*/ color:#505050;
|
||||
/*@editable*/ font-family:Helvetica;
|
||||
/*@editable*/ font-size:14px;
|
||||
/*@editable*/ line-height:150%;
|
||||
/*@editable*/ border-bottom: 1px solid #CCCCCC;
|
||||
padding-top:20px;
|
||||
padding-right:20px;
|
||||
padding-bottom:20px;
|
||||
padding-left:20px;
|
||||
/*@editable*/ text-align:left;
|
||||
}
|
||||
|
||||
.bodyContent img{
|
||||
display:inline;
|
||||
height:auto;
|
||||
max-width:560px;
|
||||
}
|
||||
|
||||
.resetPasswordLink {
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
background-color: #607d8b;
|
||||
border-radius: 25px;
|
||||
padding: 0.75rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.resetPasswordLink:hover {
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* ========== Footer Styles ========== */
|
||||
|
||||
/**
|
||||
* @tab Footer
|
||||
* @section footer style
|
||||
* @tip Set the background color and borders for your email's footer area.
|
||||
* @theme footer
|
||||
*/
|
||||
#templateFooter{
|
||||
/*@editable*/ background-color:#fff;
|
||||
/*@editable*/ border-top:1px solid #FFFFFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* @tab Footer
|
||||
* @section footer text
|
||||
* @tip Set the styling for your email's footer text. Choose a size and color that is easy to read.
|
||||
* @theme footer
|
||||
*/
|
||||
.footerContent{
|
||||
/*@editable*/ color:#808080;
|
||||
/*@editable*/ font-family:Helvetica;
|
||||
/*@editable*/ font-size:10px;
|
||||
/*@editable*/ line-height:150%;
|
||||
padding-top:20px;
|
||||
padding-right:20px;
|
||||
padding-bottom:20px;
|
||||
padding-left:20px;
|
||||
/*@editable*/ text-align:left;
|
||||
}
|
||||
|
||||
/**
|
||||
* @tab Footer
|
||||
* @section footer link
|
||||
* @tip Set the styling for your email's footer links. Choose a color that helps them stand out from your text.
|
||||
*/
|
||||
.footerContent a:link, .footerContent a:visited, /* Yahoo! Mail Override */ .footerContent a .yshortcuts, .footerContent a span /* Yahoo! Mail Override */{
|
||||
/*@editable*/ color:#606060;
|
||||
/*@editable*/ font-weight:normal;
|
||||
/*@editable*/ text-decoration:underline;
|
||||
}
|
||||
|
||||
/* /\/\/\/\/\/\/\/\/ MOBILE STYLES /\/\/\/\/\/\/\/\/ */
|
||||
|
||||
@media only screen and (max-width: 480px){
|
||||
/* /\/\/\/\/\/\/ CLIENT-SPECIFIC MOBILE STYLES /\/\/\/\/\/\/ */
|
||||
body, table, td, p, a, li, blockquote{-webkit-text-size-adjust:none !important;} /* Prevent Webkit platforms from changing default text sizes */
|
||||
body{width:100% !important; min-width:100% !important;} /* Prevent iOS Mail from adding padding to the body */
|
||||
|
||||
/* /\/\/\/\/\/\/ MOBILE RESET STYLES /\/\/\/\/\/\/ */
|
||||
#bodyCell{padding:10px !important;}
|
||||
|
||||
/* /\/\/\/\/\/\/ MOBILE TEMPLATE STYLES /\/\/\/\/\/\/ */
|
||||
|
||||
/* ======== Page Styles ======== */
|
||||
|
||||
/**
|
||||
* @tab Mobile Styles
|
||||
* @section template width
|
||||
* @tip Make the template fluid for portrait or landscape view adaptability. If a fluid layout doesn't work for you, set the width to 300px instead.
|
||||
*/
|
||||
#templateContainer{
|
||||
max-width:600px !important;
|
||||
/*@editable*/ width:100% !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* @tab Mobile Styles
|
||||
* @section heading 1
|
||||
* @tip Make the first-level headings larger in size for better readability on small screens.
|
||||
*/
|
||||
h1{
|
||||
/*@editable*/ font-size:24px !important;
|
||||
/*@editable*/ line-height:100% !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* @tab Mobile Styles
|
||||
* @section heading 2
|
||||
* @tip Make the second-level headings larger in size for better readability on small screens.
|
||||
*/
|
||||
h2{
|
||||
/*@editable*/ font-size:20px !important;
|
||||
/*@editable*/ line-height:100% !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* @tab Mobile Styles
|
||||
* @section heading 3
|
||||
* @tip Make the third-level headings larger in size for better readability on small screens.
|
||||
*/
|
||||
h3{
|
||||
/*@editable*/ font-size:18px !important;
|
||||
/*@editable*/ line-height:100% !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* @tab Mobile Styles
|
||||
* @section heading 4
|
||||
* @tip Make the fourth-level headings larger in size for better readability on small screens.
|
||||
*/
|
||||
h4{
|
||||
/*@editable*/ font-size:16px !important;
|
||||
/*@editable*/ line-height:100% !important;
|
||||
}
|
||||
|
||||
/* ======== Header Styles ======== */
|
||||
|
||||
#templatePreheader{display:none !important;} /* Hide the template preheader to save space */
|
||||
|
||||
/**
|
||||
* @tab Mobile Styles
|
||||
* @section header image
|
||||
* @tip Make the main header image fluid for portrait or landscape view adaptability, and set the image's original width as the max-width. If a fluid setting doesn't work, set the image width to half its original size instead.
|
||||
*/
|
||||
#headerImage{
|
||||
height:auto !important;
|
||||
/*@editable*/ max-width:600px !important;
|
||||
/*@editable*/ width:100% !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* @tab Mobile Styles
|
||||
* @section header text
|
||||
* @tip Make the header content text larger in size for better readability on small screens. We recommend a font size of at least 16px.
|
||||
*/
|
||||
.headerContent{
|
||||
/*@editable*/ font-size:20px !important;
|
||||
/*@editable*/ line-height:125% !important;
|
||||
}
|
||||
|
||||
/* ======== Body Styles ======== */
|
||||
|
||||
/**
|
||||
* @tab Mobile Styles
|
||||
* @section body text
|
||||
* @tip Make the body content text larger in size for better readability on small screens. We recommend a font size of at least 16px.
|
||||
*/
|
||||
.bodyContent{
|
||||
/*@editable*/ font-size:18px !important;
|
||||
/*@editable*/ line-height:125% !important;
|
||||
}
|
||||
|
||||
/* ======== Footer Styles ======== */
|
||||
|
||||
/**
|
||||
* @tab Mobile Styles
|
||||
* @section footer text
|
||||
* @tip Make the body content text larger in size for better readability on small screens.
|
||||
*/
|
||||
.footerContent{
|
||||
/*@editable*/ font-size:14px !important;
|
||||
/*@editable*/ line-height:115% !important;
|
||||
}
|
||||
|
||||
.footerContent a{display:block !important;} /* Place footer social and utility links on their own lines, for easier access */
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body leftmargin="0" marginwidth="0" topmargin="0" marginheight="0" offset="0">
|
||||
<center>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" height="100%" width="100%" id="bodyTable">
|
||||
<tr>
|
||||
<td align="center" valign="top" id="bodyCell">
|
||||
<!-- BEGIN TEMPLATE // -->
|
||||
<table border="0" cellpadding="0" cellspacing="0" id="templateContainer">
|
||||
<tr>
|
||||
<td align="center" valign="top">
|
||||
<!-- BEGIN PREHEADER // -->
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" id="templatePreheader">
|
||||
<tr>
|
||||
<td valign="top" class="preheaderContent" style="padding-top:10px; padding-right:20px; padding-bottom:10px; padding-left:20px;" mc:edit="preheader_content00">
|
||||
Welcome to Unleash!
|
||||
</td>
|
||||
<!-- *|IFNOT:ARCHIVE_PAGE|* -->
|
||||
<td valign="top" width="180" class="preheaderContent" style="padding-top:10px; padding-right:20px; padding-bottom:10px; padding-left:0;" mc:edit="preheader_content01">
|
||||
Email not displaying correctly?<br /><a href="*|ARCHIVE|*" target="_blank">View it in your browser</a>.
|
||||
</td>
|
||||
<!-- *|END:IF|* -->
|
||||
</tr>
|
||||
</table>
|
||||
<!-- // END PREHEADER -->
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top">
|
||||
<!-- BEGIN HEADER // -->
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" id="templateHeader">
|
||||
<tr>
|
||||
<td valign="top" class="headerContent">
|
||||
<img src="https://www.unleash-hosted.com/wp-content/uploads/2019/12/unleash-hosted-logo-v2-mini-fit.png" style="max-width:600px;" id="headerImage" mc:label="header_image" mc:edit="header_image" mc:allowdesigner mc:allowtext />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- // END HEADER -->
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top">
|
||||
<!-- BEGIN BODY // -->
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" id="templateBody">
|
||||
<tr>
|
||||
<td valign="top" class="bodyContent" mc:edit="body_content">
|
||||
<h1>Welcome to Unleash {{ name }}!</h1>
|
||||
|
||||
<br />
|
||||
You have been invited to your organizations Unleash account. Below is your magic sign-in link. Just click the button and follow the steps provided to update your password and get started using Unleash.
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<a class="resetPasswordLink" href="{{{ passwordLink }}}" target="_blank" rel="noopener noreferrer">Setup account<a/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td valign="top" class="bodyContent" mc:edit="body_content">
|
||||
<h2>Useful resources</h2>
|
||||
|
||||
<p>Once you are up and running, you might want to take a look at the following resources:</p>
|
||||
<ul>
|
||||
<li><a href="https://docs.getunleash.io" target="_blank" rel="noopener noreferrer">Documentation</a></li>
|
||||
<li><a href="https://github.com/unleash" target="_blank" rel="noopener noreferrer">Code examples</a></li>
|
||||
<li><a href="https://docs.getunleash.io/docs/user_guide/connect_sdk" target="_blank" rel="noopener noreferrer">SDKs</a></li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- // END BODY -->
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top">
|
||||
<!-- BEGIN FOOTER // -->
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" id="templateFooter">
|
||||
<tr>
|
||||
<td valign="top" class="footerContent" mc:edit="footer_content00">
|
||||
<a href="https://github.com/Unleash/unleash">Follow us on Github</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td valign="top" class="footerContent" style="padding-top:0;" mc:edit="footer_content01">
|
||||
<em>Copyright © {{ year }} | Unleash | All rights reserved.</em>
|
||||
<br />
|
||||
|
||||
<br />
|
||||
<strong>Our mailing address is: team@getunleash.io</strong>
|
||||
<br />
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<!-- // END FOOTER -->
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- // END TEMPLATE -->
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,11 @@
|
||||
Welcome to Unleash {{ name }}!
|
||||
|
||||
You have been invited to your organizations Unleash account. Below is your magic sign-in link. Just click the button and follow the steps provided to update your password and get started using Unleash.
|
||||
|
||||
Visit {{{ passwordLink }}} to get started.
|
||||
|
||||
Useful resources:
|
||||
|
||||
- https://docs.getunleash.io
|
||||
- https://github.com/unleash
|
||||
- https://docs.getunleash.io/docs/user_guide/connect_sdk
|
@ -499,7 +499,7 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" id="templateFooter">
|
||||
<tr>
|
||||
<td valign="top" class="footerContent" mc:edit="footer_content00">
|
||||
<a href="https://github.com/Unleash/unleash">Follow us on Github</a> <a href="*|FACEBOOK:PROFILEURL|*">Friend on Facebook</a> <a href="*|FORWARD|*">Forward to Friend</a>
|
||||
<a href="https://github.com/Unleash/unleash">Follow us on Github</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -218,3 +218,27 @@ test.serial('should search for users', async t => {
|
||||
t.true(res.body.some(u => u.email === 'another@mail.com'));
|
||||
});
|
||||
});
|
||||
|
||||
test.serial(
|
||||
'Creates a user and includes inviteLink and emailConfigured',
|
||||
async t => {
|
||||
t.plan(5);
|
||||
const request = await setupApp(stores);
|
||||
return request
|
||||
.post('/api/admin/user-admin')
|
||||
.send({
|
||||
email: 'some@getunelash.ai',
|
||||
name: 'Some Name',
|
||||
rootRole: editorRole.id,
|
||||
})
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(201)
|
||||
.expect(res => {
|
||||
t.is(res.body.email, 'some@getunelash.ai');
|
||||
t.is(res.body.rootRole, editorRole.id);
|
||||
t.truthy(res.body.inviteLink);
|
||||
t.falsy(res.body.emailSent);
|
||||
t.truthy(res.body.id);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
@ -60,7 +60,7 @@ test.serial('Should create a reset link', async t => {
|
||||
});
|
||||
|
||||
test.serial('Should create a welcome link', async t => {
|
||||
const url = await resetTokenService.createWelcomeUrl(
|
||||
const url = await resetTokenService.createNewUserUrl(
|
||||
userIdToCreateResetFor,
|
||||
adminUser.username,
|
||||
);
|
||||
@ -94,3 +94,15 @@ test.serial('Creating a new token should expire older tokens', async t => {
|
||||
const validToken = await resetTokenService.isValid(secondToken.token);
|
||||
t.is(secondToken.token, validToken.token);
|
||||
});
|
||||
|
||||
test.serial(
|
||||
'Retrieving valid invitation links should retrieve an object with userid key and token value',
|
||||
async t => {
|
||||
await resetTokenService.createToken(userIdToCreateResetFor, adminUser);
|
||||
|
||||
const activeInvitations = await resetTokenService.getActiveInvitations();
|
||||
t.true(Object.keys(activeInvitations).length === 1);
|
||||
t.true(+Object.keys(activeInvitations)[0] === userIdToCreateResetFor);
|
||||
t.truthy(activeInvitations[userIdToCreateResetFor]);
|
||||
},
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user