mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01: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> | ||||
|         <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> | ||||
| 			/* /\/\/\/\/\/\/\/\/ 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