Add job queue; e-mail sending; password reset support

This commit is contained in:
garrettmills
2020-05-25 15:45:26 -05:00
parent f371310620
commit 76ba843348
22 changed files with 884 additions and 30 deletions

View File

@@ -1,6 +1,8 @@
import { Component } from '../../../lib/vues6/vues6.js'
import { auth_api } from '../../service/AuthApi.service.js'
import { location_service } from '../../service/Location.service.js'
import { message_service } from '../../service/Message.service.js'
import { password_service } from '../../service/Password.service.js'
const template = `
<div class="coreid-login-form col-lg-6 col-md-8 col-sm-10 col-xs-12 offset-lg-3 offset-md-2 offset-sm-1 offset-xs-0 text-left">
@@ -41,6 +43,10 @@ const template = `
class="mr-3"
v-if="!step_two && !loading && registration_enabled"
><a href="#" class="text-secondary" @click="on_register_click">Need an account?</a></small>
<small
class="mr-3"
v-if="!loading"
><a href="#" class="text-secondary" @click="on_forgot_password">Forgot password?</a></small>
<button type="button" class="btn btn-primary" :disabled="loading" v-if="step_two" v-on:click="back_click">Back</button>
<button type="button" class="btn btn-primary" :disabled="loading || btn_disabled" v-on:click="step_click">{{ button_text }}</button>
</div>
@@ -147,5 +153,38 @@ export default class AuthLoginForm extends Component {
location_service.redirect('/auth/register', 1500) // TODO get this dynamically
}
async on_forgot_password() {
console.log(message_service)
await message_service.modal({
title: 'Reset Password',
message: 'If you have forgotten your password, you can request a reset e-mail to be sent to your account. Enter your e-mail address:',
inputs: [
{
type: 'email',
name: 'email',
placeholder: 'jdoe@contoso.com',
},
],
buttons: [
{
type: 'close',
text: 'Cancel',
},
{
type: 'close',
class: ['btn', 'btn-primary'],
text: 'Request',
on_click: async ($event, { email }) => {
await password_service.request_reset(email)
await message_service.alert({
type: 'success',
message: 'Success! If that e-mail address is associated with a valid ' + this.app_name + ' account, it will receive an e-mail with more instructions shortly.',
})
},
},
],
})
}
do_nothing() {}
}

View File

@@ -6,6 +6,7 @@ import MFADisableComponent from './auth/MFADisable.component.js'
import PasswordResetComponent from './auth/PasswordReset.component.js'
import InvokeActionComponent from './InvokeAction.component.js'
import RegistrationFormComponent from './auth/register/Form.component.js'
import MessageContainerComponent from './dash/message/MessageContainer.component.js'
const components = {
AuthLoginForm,
@@ -16,6 +17,7 @@ const components = {
PasswordResetComponent,
InvokeActionComponent,
RegistrationFormComponent,
MessageContainerComponent,
}
export { components }

View File

@@ -36,6 +36,16 @@ const template = `
</div>
<div class="modal-body">
{{ modal.message }}
<div v-if="Array.isArray(modal.inputs)" class="mt-4 mb-3">
<span v-for="input of modal.inputs">
<input
type="input.type"
v-model="modal.data[input.name]"
:placeholder="input.placeholder"
class="form-control"
>
</span>
</div>
</div>
<div class="modal-footer" v-if="modal.buttons && modal.buttons.length > 0">
<button
@@ -68,8 +78,8 @@ export default class MessageContainerComponent extends Component {
})
this.modal_event = event_bus.event('message.modal')
this.modal_event.subscribe(({ title, message, buttons = [] }) => {
this.create_modal(title, message, buttons)
this.modal_event.subscribe(({ title, message, buttons = [], inputs = [] }) => {
this.create_modal(title, message, buttons, inputs)
})
message_service.init_listener()
@@ -96,12 +106,14 @@ export default class MessageContainerComponent extends Component {
}
}
create_modal(title, message, buttons = []) {
create_modal(title, message, buttons = [], inputs = []) {
const index = this.modals.length
const modal = {
title,
message,
buttons
buttons,
inputs,
data: {},
}
this.modals.push(modal)
@@ -112,7 +124,7 @@ export default class MessageContainerComponent extends Component {
modal_button_click($event, modal, button) {
if ( typeof button.on_click === 'function' ) {
button.on_click($event)
button.on_click($event, modal.data)
}
}
}

View File

@@ -7,8 +7,8 @@ class MessageService {
event_bus.event('message.alert').fire({ type, message, timeout, on_dismiss })
}
modal({title, message, buttons = [] }) {
event_bus.event('message.modal').fire({ title, message, buttons })
modal({title, message, buttons = [], inputs = []}) {
event_bus.event('message.modal').fire({ title, message, buttons, inputs })
}
async fetch() {
@@ -24,7 +24,11 @@ class MessageService {
this.message_ids = []
this.listener = setInterval(() => this._listener_tick(), this.listener_interval)
window.addEventListener('beforeunload', () => this.stop_listener())
this._listener_tick()
try {
this._listener_tick()
} catch (e) {
this.stop_listener()
}
}
async _listener_tick() {

View File

@@ -7,6 +7,10 @@ class PasswordService {
async reset(password) {
await axios.post('/api/v1/password/resets', { password })
}
async request_reset(email) {
await axios.post('/api/v1/password/request_reset', { email })
}
}
const password_service = new PasswordService()

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

View File

@@ -3,7 +3,7 @@ const zxcvbn = require('zxcvbn')
class PasswordController extends Controller {
static get services() {
return [...super.services, 'auth']
return [...super.services, 'auth', 'jobs', 'models']
}
async get_resets(req, res, next) {
@@ -92,8 +92,26 @@ class PasswordController extends Controller {
const flitter = await this.auth.get_provider('flitter')
await flitter.logout(req)
await req.user.kickout()
req.trust.unassume()
return res.api()
}
async request_reset(req, res, next) {
if ( !req.body.email )
return res.status(400)
.message('Missing required field: email')
.api()
const User = this.models.get('auth:User')
const user = await User.findOne({ email: req.body.email })
if ( user ) {
const reset_queue = this.jobs.queue('password_resets')
await reset_queue.add('PasswordReset', { user_id: user.id })
}
return res.api({ success: true })
}
}
module.exports = exports = PasswordController

View File

@@ -12,6 +12,13 @@ class PasswordController extends Controller {
})
}
async password_reset_keyaction(req, res, next) {
req.user.trap = 'password_reset'
await req.user.save()
req.trust.assume()
return res.redirect('/auth/password/reset')
}
}
module.exports = exports = PasswordController

27
app/jobs/EMail.job.js Normal file
View File

@@ -0,0 +1,27 @@
const { Job } = require('flitter-jobs')
class EMailJob extends Job {
static get services() {
return [...super.services, 'smtp', 'output']
}
async execute(job) {
const config = this.smtp.config()
const transport = this.smtp.transport()
const { data } = job
const { from = config.default_sender, to, subject, html } = data
this.output.info(`Sending mail to ${to}...`)
try {
await transport.sendMail({
from, to, subject, html,
})
} catch (e) {
this.output.error(e)
}
this.output.success(`Mail sent!`)
}
}
module.exports = exports = EMailJob

View File

@@ -0,0 +1,497 @@
const { Job } = require('flitter-jobs')
class PasswordResetJob extends Job {
static get services() {
return [...super.services, 'models', 'jobs', 'output', 'configs']
}
async execute(job) {
const {data} = job
const {user_id} = data
try {
const User = this.models.get('auth:User')
const user = await User.findById(user_id)
if (!user) {
this.output.error(`Unable to find user with ID: ${user_id}`)
throw new Error('Unable to find user with that ID.')
}
this.output.info(`Resetting password for user: ${user.uid}`)
// Create an authenticated key-action
const key_action = await this.key_action(user)
const email = this.email(key_action.url())
await this.jobs.queue('mailer').add('EMail', {
to: user.email,
subject: 'Reset Your Password | ' + this.configs.get('app.name'),
html: email,
})
this.output.success('Password reset logged.')
} catch (e) {
this.output.error(e)
}
}
async key_action(user) {
const KeyAction = this.models.get('auth:KeyAction')
const ka_data = {
handler: 'controller::auth:Password.password_reset_keyaction',
used: false,
user_id: user._id,
auto_login: true,
no_auto_logout: true,
}
return (new KeyAction(ka_data)).save()
}
email(keyaction_link) {
const app_name = this.configs.get('app.name')
const login_url = this.configs.get('app.url') + 'auth/login'
const image_url = this.configs.get('app.url') + 'assets/reset_pass_email_image.jpg'
return `
<!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" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<!--[if gte mso 9]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
<meta content="width=device-width" name="viewport"/>
<!--[if !mso]><!-->
<meta content="IE=edge" http-equiv="X-UA-Compatible"/>
<!--<![endif]-->
<title></title>
<!--[if !mso]><!-->
<!--<![endif]-->
<style type="text/css">
body {
margin: 0;
padding: 0;
}
table,
td,
tr {
vertical-align: top;
border-collapse: collapse;
}
* {
line-height: inherit;
}
a[x-apple-data-detectors=true] {
color: inherit !important;
text-decoration: none !important;
}
</style>
<style id="media-query" type="text/css">
@media (max-width: 660px) {
.block-grid,
.col {
min-width: 320px !important;
max-width: 100% !important;
display: block !important;
}
.block-grid {
width: 100% !important;
}
.col {
width: 100% !important;
}
.col>div {
margin: 0 auto;
}
img.fullwidth,
img.fullwidthOnMobile {
max-width: 100% !important;
}
.no-stack .col {
min-width: 0 !important;
display: table-cell !important;
}
.no-stack.two-up .col {
width: 50% !important;
}
.no-stack .col.num4 {
width: 33% !important;
}
.no-stack .col.num8 {
width: 66% !important;
}
.no-stack .col.num4 {
width: 33% !important;
}
.no-stack .col.num3 {
width: 25% !important;
}
.no-stack .col.num6 {
width: 50% !important;
}
.no-stack .col.num9 {
width: 75% !important;
}
.video-block {
max-width: none !important;
}
.mobile_hide {
min-height: 0px;
max-height: 0px;
max-width: 0px;
display: none;
overflow: hidden;
font-size: 0px;
}
.desktop_hide {
display: block !important;
max-height: none !important;
}
}
</style>
</head>
<body class="clean-body" style="margin: 0; padding: 0; -webkit-text-size-adjust: 100%; background-color: #f8f8f9;">
<!--[if IE]>
<div class="ie-browser">
<![endif]-->
<table bgcolor="#f8f8f9" cellpadding="0" cellspacing="0" class="nl-container" role="presentation" style="table-layout: fixed; vertical-align: top; min-width: 320px; Margin: 0 auto; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #f8f8f9; width: 100%;" valign="top" width="100%">
<tbody>
<tr style="vertical-align: top;" valign="top">
<td style="word-break: break-word; vertical-align: top;" valign="top">
<!--[if (mso)|(IE)]>
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td align="center" style="background-color:#f8f8f9">
<![endif]-->
<div style="background-color:#1aa19c;">
<div class="block-grid" style="Margin: 0 auto; min-width: 320px; max-width: 640px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: #1aa19c;">
<div style="border-collapse: collapse;display: table;width: 100%;background-color:#1aa19c;">
<!--[if (mso)|(IE)]>
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:#1aa19c;">
<tr>
<td align="center">
<table cellpadding="0" cellspacing="0" border="0" style="width:640px">
<tr class="layout-full-width" style="background-color:#1aa19c">
<![endif]-->
<!--[if (mso)|(IE)]>
<td align="center" width="640" style="background-color:#1aa19c;width:640px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top">
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td style="padding-right: 0px; padding-left: 0px; padding-top:0px; padding-bottom:0px;">
<![endif]-->
<div class="col num12" style="min-width: 320px; max-width: 640px; display: table-cell; vertical-align: top; width: 640px;">
<div style="width:100% !important;">
<!--[if (!mso)&(!IE)]><!-->
<div style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:0px; padding-bottom:0px; padding-right: 0px; padding-left: 0px;">
<!--<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" class="divider" role="presentation" style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;" valign="top" width="100%">
<tbody>
<tr style="vertical-align: top;" valign="top">
<td class="divider_inner" style="word-break: break-word; vertical-align: top; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px;" valign="top">
<table align="center" border="0" cellpadding="0" cellspacing="0" class="divider_content" role="presentation" style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; border-top: 4px solid #1AA19C; width: 100%;" valign="top" width="100%">
<tbody>
<tr style="vertical-align: top;" valign="top">
<td style="word-break: break-word; vertical-align: top; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;" valign="top"><span></span></td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<!--[if (!mso)&(!IE)]><!-->
</div>
<!--<![endif]-->
</div>
</div>
<!--[if (mso)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
<!--[if (mso)|(IE)]>
</td>
</tr>
</table>
</td>
</tr>
</table>
<![endif]-->
</div>
</div>
</div>
<div style="background-color:#fff;">
<div class="block-grid" style="Margin: 0 auto; min-width: 320px; max-width: 640px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: #fff;">
<div style="border-collapse: collapse;display: table;width: 100%;background-color:#fff;">
<!--[if (mso)|(IE)]>
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:#fff;">
<tr>
<td align="center">
<table cellpadding="0" cellspacing="0" border="0" style="width:640px">
<tr class="layout-full-width" style="background-color:#fff">
<![endif]-->
<!--[if (mso)|(IE)]>
<td align="center" width="640" style="background-color:#fff;width:640px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top">
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td style="padding-right: 0px; padding-left: 0px; padding-top:0px; padding-bottom:0px;">
<![endif]-->
<div class="col num12" style="min-width: 320px; max-width: 640px; display: table-cell; vertical-align: top; width: 640px;">
<div style="width:100% !important;">
<!--[if (!mso)&(!IE)]><!-->
<div style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:0px; padding-bottom:0px; padding-right: 0px; padding-left: 0px;">
<!--<![endif]-->
<!--[if mso]>
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Tahoma, sans-serif">
<![endif]-->
<div style="color:#555555;font-family:Montserrat, Trebuchet MS, Lucida Grande, Lucida Sans Unicode, Lucida Sans, Tahoma, sans-serif;line-height:1.2;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;">
<div style="line-height: 1.2; font-size: 12px; color: #555555; font-family: Montserrat, Trebuchet MS, Lucida Grande, Lucida Sans Unicode, Lucida Sans, Tahoma, sans-serif; mso-line-height-alt: 14px;">
<p style="font-size: 30px; line-height: 1.2; word-break: break-word; text-align: center; mso-line-height-alt: 36px; margin: 0;margin-top: 80px;"><span style="font-size: 30px;">${app_name}</span></p>
</div>
</div>
<!--[if mso]>
</td>
</tr>
</table>
<![endif]-->
<!--[if (!mso)&(!IE)]><!-->
</div>
<!--<![endif]-->
</div>
</div>
<!--[if (mso)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
<!--[if (mso)|(IE)]>
</td>
</tr>
</table>
</td>
</tr>
</table>
<![endif]-->
</div>
</div>
</div>
<div style="background-color:transparent;">
<div class="block-grid" style="Margin: 0 auto; min-width: 320px; max-width: 640px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: #fff;">
<div style="border-collapse: collapse;display: table;width: 100%;background-color:#fff;">
<!--[if (mso)|(IE)]>
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;">
<tr>
<td align="center">
<table cellpadding="0" cellspacing="0" border="0" style="width:640px">
<tr class="layout-full-width" style="background-color:#fff">
<![endif]-->
<!--[if (mso)|(IE)]>
<td align="center" width="640" style="background-color:#fff;width:640px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top">
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td style="padding-right: 0px; padding-left: 0px; padding-top:0px; padding-bottom:0px;">
<![endif]-->
<div class="col num12" style="min-width: 320px; max-width: 640px; display: table-cell; vertical-align: top; width: 640px;">
<div style="width:100% !important;">
<!--[if (!mso)&(!IE)]><!-->
<div style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:0px; padding-bottom:0px; padding-right: 0px; padding-left: 0px;">
<!--<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" class="divider" role="presentation" style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;" valign="top" width="100%">
<tbody>
<tr style="vertical-align: top;" valign="top">
<td class="divider_inner" style="word-break: break-word; vertical-align: top; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; padding-top: 50px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px;" valign="top">
<table align="center" border="0" cellpadding="0" cellspacing="0" class="divider_content" role="presentation" style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; border-top: 0px solid #BBBBBB; width: 100%;" valign="top" width="100%">
<tbody>
<tr style="vertical-align: top;" valign="top">
<td style="word-break: break-word; vertical-align: top; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;" valign="top"><span></span></td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<!--[if mso]>
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td style="padding-right: 40px; padding-left: 40px; padding-top: 10px; padding-bottom: 10px; font-family: Tahoma, sans-serif">
<![endif]-->
<div style="color:#555555;font-family:Montserrat, Trebuchet MS, Lucida Grande, Lucida Sans Unicode, Lucida Sans, Tahoma, sans-serif;line-height:1.2;padding-top:10px;padding-right:40px;padding-bottom:10px;padding-left:40px;">
<div style="line-height: 1.2; font-size: 12px; color: #555555; font-family: Montserrat, Trebuchet MS, Lucida Grande, Lucida Sans Unicode, Lucida Sans, Tahoma, sans-serif; mso-line-height-alt: 14px;">
<p style="font-size: 30px; line-height: 1.2; text-align: center; word-break: break-word; mso-line-height-alt: 36px; margin: 0;"><span style="font-size: 30px; color: #2b303a;"><strong>Forgot Password?</strong></span></p>
</div>
</div>
<!--[if mso]>
</td>
</tr>
</table>
<![endif]-->
<!--[if mso]>
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td style="padding-right: 40px; padding-left: 40px; padding-top: 10px; padding-bottom: 10px; font-family: Tahoma, sans-serif">
<![endif]-->
<div style="color:#555555;font-family:Montserrat, Trebuchet MS, Lucida Grande, Lucida Sans Unicode, Lucida Sans, Tahoma, sans-serif;line-height:1.5;padding-top:10px;padding-right:40px;padding-bottom:10px;padding-left:40px;">
<div style="line-height: 1.5; font-size: 12px; font-family: Montserrat, Trebuchet MS, Lucida Grande, Lucida Sans Unicode, Lucida Sans, Tahoma, sans-serif; color: #555555; mso-line-height-alt: 18px;">
<p style="font-size: 15px; line-height: 1.5; text-align: center; word-break: break-word; font-family: inherit; mso-line-height-alt: 23px; margin: 0;"><span style="color: #808389; font-size: 15px;">It looks like you requested a password reset for your account. Click the button below, and we'll walk you through creating a new one.<br/></span></p>
<p style="font-size: 14px; line-height: 1.5; text-align: center; word-break: break-word; font-family: inherit; mso-line-height-alt: 21px; margin: 0;"> </p>
<p style="font-size: 15px; line-height: 1.5; text-align: center; word-break: break-word; font-family: inherit; mso-line-height-alt: 23px; margin: 0;"><span style="color: #808389; font-size: 15px;">If you didn't request this e-mail, please contact your system administrator.</span></p>
</div>
</div>
<!--[if mso]>
</td>
</tr>
</table>
<![endif]-->
<div align="center" class="button-container" style="padding-top:15px;padding-right:10px;padding-bottom:0px;padding-left:10px;">
<!--[if mso]>
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-spacing: 0; border-collapse: collapse; mso-table-lspace:0pt; mso-table-rspace:0pt;">
<tr>
<td style="padding-top: 15px; padding-right: 10px; padding-bottom: 0px; padding-left: 10px" align="center">
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="" style="height:46.5pt; width:189.75pt; v-text-anchor:middle;" arcsize="97%" stroke="false" fillcolor="#1aa19c">
<w:anchorlock/>
<v:textbox inset="0,0,0,0">
<center style="color:#ffffff; font-family:Tahoma, sans-serif; font-size:16px">
<![endif]-->
<a href="${keyaction_link}" target="_blank"><div style="text-decoration:none;display:inline-block;color:#ffffff;background-color:#1aa19c;border-radius:60px;-webkit-border-radius:60px;-moz-border-radius:60px;width:auto; width:auto;;border-top:1px solid #1aa19c;border-right:1px solid #1aa19c;border-bottom:1px solid #1aa19c;border-left:1px solid #1aa19c;padding-top:15px;padding-bottom:15px;font-family:Montserrat, Trebuchet MS, Lucida Grande, Lucida Sans Unicode, Lucida Sans, Tahoma, sans-serif;text-align:center;mso-border-alt:none;word-break:keep-all;"><span style="padding-left:30px;padding-right:30px;font-size:16px;display:inline-block;"><span style="font-size: 16px; margin: 0; line-height: 2; word-break: break-word; mso-line-height-alt: 32px;"><strong>Reset Password</strong></span></span></div></a>
<!--[if mso]>
</center>
</v:textbox>
</v:roundrect>
</td>
</tr>
</table>
<![endif]-->
</div>
<table border="0" cellpadding="0" cellspacing="0" class="divider" role="presentation" style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;" valign="top" width="100%">
<tbody>
<tr style="vertical-align: top;" valign="top">
<td class="divider_inner" style="word-break: break-word; vertical-align: top; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; padding-top: 60px; padding-right: 0px; padding-bottom: 12px; padding-left: 0px;" valign="top">
<table align="center" border="0" cellpadding="0" cellspacing="0" class="divider_content" role="presentation" style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; border-top: 0px solid #BBBBBB; width: 100%;" valign="top" width="100%">
<tbody>
<tr style="vertical-align: top;" valign="top">
<td style="word-break: break-word; vertical-align: top; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;" valign="top"><span></span></td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<!--[if (!mso)&(!IE)]><!-->
</div>
<!--<![endif]-->
</div>
</div>
<!--[if (mso)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
<!--[if (mso)|(IE)]>
</td>
</tr>
</table>
</td>
</tr>
</table>
<![endif]-->
</div>
</div>
</div>
<div style="background-color:transparent;">
<div class="block-grid" style="Margin: 0 auto; min-width: 320px; max-width: 640px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: #2b303a;">
<div style="border-collapse: collapse;display: table;width: 100%;background-color:#2b303a;">
<!--[if (mso)|(IE)]>
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;">
<tr>
<td align="center">
<table cellpadding="0" cellspacing="0" border="0" style="width:640px">
<tr class="layout-full-width" style="background-color:#2b303a">
<![endif]-->
<!--[if (mso)|(IE)]>
<td align="center" width="640" style="background-color:#2b303a;width:640px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top">
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td style="padding-right: 0px; padding-left: 0px; padding-top:0px; padding-bottom:0px;">
<![endif]-->
<div class="col num12" style="min-width: 320px; max-width: 640px; display: table-cell; vertical-align: top; width: 640px;">
<div style="width:100% !important;">
<!--[if (!mso)&(!IE)]><!-->
<div style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:0px; padding-bottom:0px; padding-right: 0px; padding-left: 0px;">
<!--<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" class="divider" role="presentation" style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;" valign="top" width="100%">
<tbody>
<tr style="vertical-align: top;" valign="top">
<td class="divider_inner" style="word-break: break-word; vertical-align: top; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px;" valign="top">
<table align="center" border="0" cellpadding="0" cellspacing="0" class="divider_content" role="presentation" style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; border-top: 4px solid #1AA19C; width: 100%;" valign="top" width="100%">
<tbody>
<tr style="vertical-align: top;" valign="top">
<td style="word-break: break-word; vertical-align: top; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;" valign="top"><span></span></td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<!--[if mso]>
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td style="padding-right: 40px; padding-left: 40px; padding-top: 20px; padding-bottom: 30px; font-family: Tahoma, sans-serif">
<![endif]-->
<div style="color:#555555;font-family:Montserrat, Trebuchet MS, Lucida Grande, Lucida Sans Unicode, Lucida Sans, Tahoma, sans-serif;line-height:1.2;padding-top:20px;padding-right:40px;padding-bottom:30px;padding-left:40px;">
<div style="line-height: 1.2; font-size: 12px; font-family: Montserrat, Trebuchet MS, Lucida Grande, Lucida Sans Unicode, Lucida Sans, Tahoma, sans-serif; color: #555555; mso-line-height-alt: 14px;">
<p style="font-size: 12px; line-height: 1.2; word-break: break-word; text-align: left; font-family: inherit; mso-line-height-alt: 14px; margin: 0;"><span style="color: #95979c; font-size: 12px;">${app_name} | </span><span style="font-size: 12px; color: #ffffff;"><a href="${login_url}" rel="noopener" style="text-decoration: underline; color: #ffffff;" target="_blank">Login</a></span><span style="color: #95979c; font-size: 12px;"><br/></span></p>
</div>
</div>
<!--[if mso]>
</td>
</tr>
</table>
<![endif]-->
<!--[if (!mso)&(!IE)]><!-->
</div>
<!--<![endif]-->
</div>
</div>
<!--[if (mso)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
<!--[if (mso)|(IE)]>
</td>
</tr>
</table>
</td>
</tr>
</table>
<![endif]-->
</div>
</div>
</div>
<!--[if (mso)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
<!--[if (IE)]>
</div>
<![endif]-->
</body>
</html>
`
}
}
module.exports = exports = PasswordResetJob

View File

@@ -70,7 +70,7 @@ class TrustManager {
}
end() {
const next = this.request.session.trust_flow.next
const next = this.request.session?.trust_flow?.next
delete this.request.session.trust_flow
return next
}

View File

@@ -58,7 +58,10 @@ const index = {
* or middleware that are applied in order.
*/
post: {
'/api/v1/password/request_reset': [
'middleware::auth:GuestOnly',
'controller::api:v1:Password.request_reset',
],
},
}

29
app/unit/SMTPUnit.js Normal file
View File

@@ -0,0 +1,29 @@
const { Unit } = require('libflitter')
const nodemailer = require('nodemailer')
class SMTPUnit extends Unit {
static get name() { return 'smtp' }
static get services() {
return [...super.services, 'configs']
}
config() {
return this.configs.get('smtp')
}
transport() {
return this._transport
}
async go(app) {
const config = this.config()
this._transport = nodemailer.createTransport({
host: config.host,
port: config.port,
secure: config.secure,
auth: config.auth,
})
}
}
module.exports = exports = SMTPUnit

View File

@@ -31,4 +31,5 @@ block app
.col-12.text-center
block masthead
#vue-app-base
coreid-message-container
block vue