Add job queue; e-mail sending; password reset support
This commit is contained in:
parent
f371310620
commit
76ba843348
@ -1,5 +1,3 @@
|
||||
- MFA recovery codes handling
|
||||
- Forgot password handling
|
||||
- Admin password reset mechanism -> flag users as needing PW resets
|
||||
- OAuth2 -> support refresh tokens
|
||||
- Traps -> not clearing trust?
|
||||
|
@ -31,9 +31,12 @@ const FlitterUnits = {
|
||||
* Custom units that modify or add functionality that needs to be made
|
||||
* available to the middleware-routing-controller stack.
|
||||
*/
|
||||
'Redis' : require('flitter-redis/src/RedisUnit'),
|
||||
'Jobs' : require('flitter-jobs/src/JobsUnit'),
|
||||
'Settings' : require('./app/unit/SettingsUnit'),
|
||||
'Upload' : require('flitter-upload/UploadUnit'),
|
||||
'Less' : require('flitter-less/LessUnit'),
|
||||
'SMTP' : require('./app/unit/SMTPUnit'),
|
||||
'LDAPServer' : require('./app/unit/LDAPServerUnit'),
|
||||
'LDAPMiddleware': require('./app/unit/LDAPMiddlewareUnit'),
|
||||
'LDAPController': require('./app/unit/LDAPControllerUnit'),
|
||||
|
@ -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() {}
|
||||
}
|
||||
|
@ -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 }
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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()
|
||||
|
BIN
app/assets/reset_pass_email_image.jpg
Normal file
BIN
app/assets/reset_pass_email_image.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 71 KiB |
@ -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
|
||||
|
@ -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
27
app/jobs/EMail.job.js
Normal 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
|
497
app/jobs/PasswordReset.job.js
Normal file
497
app/jobs/PasswordReset.job.js
Normal 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
|
@ -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
|
||||
}
|
||||
|
@ -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
29
app/unit/SMTPUnit.js
Normal 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
|
@ -31,4 +31,5 @@ block app
|
||||
.col-12.text-center
|
||||
block masthead
|
||||
#vue-app-base
|
||||
coreid-message-container
|
||||
block vue
|
||||
|
35
config/jobs.config.js
Normal file
35
config/jobs.config.js
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Job Queue Configuration
|
||||
* -------------------------------------------------
|
||||
* This file is provided by flitter-jobs and defines the job
|
||||
* queues available to the application. It also specifies the
|
||||
* workers and which queues they process.
|
||||
*
|
||||
* You can start a worker process by running the command:
|
||||
* ./flitter worker <worker name>
|
||||
*/
|
||||
const jobs_config = {
|
||||
|
||||
// Array of queues by name
|
||||
queues: [
|
||||
'mailer',
|
||||
'password_resets',
|
||||
],
|
||||
|
||||
// Mapping of worker name => worker config
|
||||
workers: {
|
||||
|
||||
// The name of the worker is "main"
|
||||
main: {
|
||||
// This worker will process these queues
|
||||
queues: ['mailer', 'password_resets'],
|
||||
},
|
||||
|
||||
// You can have many workers, and multiple workers can
|
||||
// process the same queue. Likewise, you can have multiple
|
||||
// worker processes of the same type.
|
||||
// (e.g. you can have two "main" workers)
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = exports = jobs_config
|
17
config/redis.config.js
Normal file
17
config/redis.config.js
Normal file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Configuration for Flitter-Redis.
|
||||
* You can access the IORedis instance on the redis service:
|
||||
* app.di().service('redis').client()
|
||||
*/
|
||||
const redis_config = {
|
||||
|
||||
// How to connect to the server
|
||||
// See IORedis docs for config options:
|
||||
// https://github.com/luin/ioredis#connect-to-redis
|
||||
server: {
|
||||
host: env('REDIS_HOST', 'localhost'),
|
||||
port: env('REDIS_PORT', 6379),
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = exports = redis_config
|
12
config/smtp.config.js
Normal file
12
config/smtp.config.js
Normal file
@ -0,0 +1,12 @@
|
||||
const smtp_config = {
|
||||
host: env('SMTP_HOST', 'localhost'),
|
||||
port: env('SMTP_PORT', 587),
|
||||
secure: env('SMTP_SECURE', false),
|
||||
default_sender: env('SMTP_DEFAULT_SENDER'),
|
||||
auth: {
|
||||
user: env('SMTP_USER'),
|
||||
pass: env('SMTP_PASS'),
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = exports = smtp_config
|
@ -15,3 +15,12 @@ ENVIRONMENT=production
|
||||
SSL_ENABLE=false
|
||||
SSL_CERT_FILE=cert.pem
|
||||
SSL_CERT_KEY=cert.key
|
||||
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
|
||||
SMTP_HOST="mail.mydomain.com"
|
||||
SMTP_PORT="587"
|
||||
SMTP_USER="coreid@mydomain.com"
|
||||
SMTP_DEFAULT_SENDER="coreid@mydomain.com"
|
||||
SMTP_PASS="supersecretpassword"
|
||||
|
10
package.json
10
package.json
@ -16,19 +16,25 @@
|
||||
"author": "Garrett Mills <garrett@glmdev.tech> (https://garrettmills.dev/)",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bullmq": "^1.8.8",
|
||||
"email-validator": "^2.0.4",
|
||||
"flitter-auth": "^0.19.0",
|
||||
"flitter-auth": "^0.19.1",
|
||||
"flitter-cli": "^0.16.0",
|
||||
"flitter-di": "^0.5.0",
|
||||
"flitter-flap": "^0.5.2",
|
||||
"flitter-forms": "^0.8.1",
|
||||
"flitter-jobs": "^0.1.2",
|
||||
"flitter-less": "^0.5.3",
|
||||
"flitter-orm": "^0.4.0",
|
||||
"flitter-redis": "^0.1.1",
|
||||
"flitter-upload": "^0.8.1",
|
||||
"ioredis": "^4.17.1",
|
||||
"is-absolute-url": "^3.0.3",
|
||||
"ldapjs": "^1.0.2",
|
||||
"libflitter": "^0.52.0",
|
||||
"libflitter": "^0.52.1",
|
||||
"moment": "^2.24.0",
|
||||
"mongodb": "^3.5.6",
|
||||
"nodemailer": "^6.4.6",
|
||||
"qrcode": "^1.4.4",
|
||||
"samlp": "^3.4.1",
|
||||
"speakeasy": "^2.0.0",
|
||||
|
161
yarn.lock
161
yarn.lock
@ -267,6 +267,18 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
|
||||
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
|
||||
|
||||
"@types/ioredis@^4.0.13":
|
||||
version "4.16.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/ioredis/-/ioredis-4.16.2.tgz#192c487a003463a6881dbc29faaa168902ef851a"
|
||||
integrity sha512-b1wSWCLx5ygzDZ2d1BYBzsTzUcPEOcJH8d5O6AJGGJXjE6C+HrlA61sw5GQNqtqTPVo2Hpc4rPA30lI0T6QSLQ==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/node@*":
|
||||
version "14.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.5.tgz#3d03acd3b3414cf67faf999aed11682ed121f22b"
|
||||
integrity sha512-90hiq6/VqtQgX8Sp0EzeIsv3r+ellbGj4URKj5j30tLlZvRUpnAe9YbYnjl3pJM93GyXU0tghHhvXHq+5rnCKA==
|
||||
|
||||
abbrev@1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
|
||||
@ -877,6 +889,20 @@ buffer@^5.4.3:
|
||||
base64-js "^1.0.2"
|
||||
ieee754 "^1.1.4"
|
||||
|
||||
bullmq@^1.8.8:
|
||||
version "1.8.8"
|
||||
resolved "https://registry.yarnpkg.com/bullmq/-/bullmq-1.8.8.tgz#659eb79a16398d38b2e8f7e9c97deb46cff65769"
|
||||
integrity sha512-ygaKtISBqMEfwrbDzhVPEtLS/FxW+SA54RzN+m8Y4imS9q/QaCXcFPME/MbvpNEXi7wAj7Uy7cnio+7yUABYSQ==
|
||||
dependencies:
|
||||
"@types/ioredis" "^4.0.13"
|
||||
cron-parser "^2.7.3"
|
||||
get-port "^5.0.0"
|
||||
ioredis "^4.3.0"
|
||||
lodash "^4.17.11"
|
||||
semver "^6.3.0"
|
||||
tslib "^1.10.0"
|
||||
uuid "^3.3.3"
|
||||
|
||||
bunyan@1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/bunyan/-/bunyan-1.3.3.tgz#bf4e301c1f0bf888ec678829531f7b5d212e9e81"
|
||||
@ -1064,6 +1090,11 @@ clone@^2.1.2:
|
||||
resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
|
||||
integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=
|
||||
|
||||
cluster-key-slot@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d"
|
||||
integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==
|
||||
|
||||
code-point-at@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
|
||||
@ -1237,6 +1268,14 @@ core-util-is@1.0.2, core-util-is@~1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
|
||||
|
||||
cron-parser@^2.7.3:
|
||||
version "2.14.0"
|
||||
resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.14.0.tgz#a8f4e85a58a4e7d59dc42cdfca76865982b68612"
|
||||
integrity sha512-/VuS5TLnyaB0yNznygEFmujOjn8DxyZRn3F2wN3h8e+4A5zETQYMbtCLCIvz23XhI/1di2B+ke702/grEaPfTg==
|
||||
dependencies:
|
||||
is-nan "^1.3.0"
|
||||
moment-timezone "^0.5.31"
|
||||
|
||||
cross-spawn@^7.0.0:
|
||||
version "7.0.2"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.2.tgz#d0d7dcfa74e89115c7619f4f721a94e1fdb716d6"
|
||||
@ -1362,7 +1401,7 @@ delegates@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
||||
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
|
||||
|
||||
denque@^1.4.1:
|
||||
denque@^1.1.0, denque@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.1.tgz#6744ff7641c148c3f8a69c307e51235c1f4a37cf"
|
||||
integrity sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==
|
||||
@ -1779,10 +1818,10 @@ flat@^4.1.0:
|
||||
dependencies:
|
||||
is-buffer "~2.0.3"
|
||||
|
||||
flitter-auth@^0.19.0:
|
||||
version "0.19.0"
|
||||
resolved "https://registry.yarnpkg.com/flitter-auth/-/flitter-auth-0.19.0.tgz#fa6d0b44c8edfed45c1f0d7de65b9acb8d444c19"
|
||||
integrity sha512-WoNkIGG981Zy3L0qqvml0rpxwNyfVAfAXjvZE6i6XnDJeLdsqHxCAVPllJlOhfJmuFPCr2TGXPl8WhAQaoG6Bw==
|
||||
flitter-auth@^0.19.1:
|
||||
version "0.19.1"
|
||||
resolved "https://registry.yarnpkg.com/flitter-auth/-/flitter-auth-0.19.1.tgz#99bb54c5e67263f111195f330b37db2d975c37d6"
|
||||
integrity sha512-f1cmpRIAp6ohO9P7En0it08s3OLKIkBED/m+7VvKwcAX+KoeQ5zWszIVHeO+u3sXq3hdw2OTmS9cx0yhXDtVnw==
|
||||
dependencies:
|
||||
axios "^0.19.0"
|
||||
bcrypt "^3.0.4"
|
||||
@ -1849,6 +1888,14 @@ flitter-forms@^0.8.1:
|
||||
recursive-readdir "^2.2.2"
|
||||
validator "^10.11.0"
|
||||
|
||||
flitter-jobs@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/flitter-jobs/-/flitter-jobs-0.1.2.tgz#5536bb12be728b61f6e0940b6c18760e4f1b59d6"
|
||||
integrity sha512-bxJD3akDnoKPRyIXXy8ytlypGI8uj0Fjn2C8gIY2HFxZeFIBJo47MqQ9qZvVWFS28pS6aUY+0emOvTFkuG/2zA==
|
||||
dependencies:
|
||||
bullmq "^1.8.8"
|
||||
flitter-redis "^0.1.1"
|
||||
|
||||
flitter-less@^0.5.3:
|
||||
version "0.5.3"
|
||||
resolved "https://registry.yarnpkg.com/flitter-less/-/flitter-less-0.5.3.tgz#afff463fee489325f3e1d8f1bf1e837ffb45150e"
|
||||
@ -1856,10 +1903,10 @@ flitter-less@^0.5.3:
|
||||
dependencies:
|
||||
express-less "^0.1.0"
|
||||
|
||||
flitter-orm@^0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/flitter-orm/-/flitter-orm-0.3.1.tgz#c08a6102e04155d3bfa27ab9bd3ad5d045e81888"
|
||||
integrity sha512-7vmtelZj+PU7TEgEucxmsJqj2s1PXL2mr2CaqgNCrozImC/rDGp17wPuHNZWlp4w1PnTNCsy3ZAU8sf9Q/3Tdw==
|
||||
flitter-orm@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/flitter-orm/-/flitter-orm-0.4.0.tgz#7903d2534cd9db18b6bad1848cb84805cb637cdb"
|
||||
integrity sha512-jOBmxV11jWEFlDtG81zexrO1wIxlnTnbGsX5KVRAw/pa/9ZBfI6wWF02Nyz1QQdsT4uRYwTIPECFudrFb8l4NA==
|
||||
dependencies:
|
||||
chai "^4.2.0"
|
||||
dotenv "^8.2.0"
|
||||
@ -1871,6 +1918,14 @@ flitter-orm@^0.3.1:
|
||||
sinon "^9.0.0"
|
||||
uuid "^3.4.0"
|
||||
|
||||
flitter-redis@^0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/flitter-redis/-/flitter-redis-0.1.1.tgz#170afe93ce33e9ed8bfca98db282224f2ce8ead9"
|
||||
integrity sha512-onAXH3r7zGh8YXrjF69uCS1WpWxjI/s9m/APqQXPGQbbekOOqb5vWV8kTsvZWh4Cx4V13WNwmXWPCD5eG0w7mg==
|
||||
dependencies:
|
||||
ioredis "^4.17.1"
|
||||
ncp "^2.0.0"
|
||||
|
||||
flitter-upload@^0.8.1:
|
||||
version "0.8.1"
|
||||
resolved "https://registry.yarnpkg.com/flitter-upload/-/flitter-upload-0.8.1.tgz#c69c2b16f8f5227532fe28373de4c1f8094fcdf6"
|
||||
@ -2001,6 +2056,11 @@ get-func-name@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
|
||||
integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=
|
||||
|
||||
get-port@^5.0.0:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193"
|
||||
integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==
|
||||
|
||||
get-stdin@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
|
||||
@ -2294,6 +2354,21 @@ invert-kv@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
|
||||
integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY=
|
||||
|
||||
ioredis@^4.17.1, ioredis@^4.3.0:
|
||||
version "4.17.1"
|
||||
resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.17.1.tgz#06ef3d3b2cb96b7e6bc90a7b8839a33e743843ad"
|
||||
integrity sha512-kfxkN/YO1dnyaoAGyNdH3my4A1eoGDy4QOfqn6o86fo4dTboxyxYVW0S0v/d3MkwCWlvSWhlwq6IJMY9BlWs6w==
|
||||
dependencies:
|
||||
cluster-key-slot "^1.1.0"
|
||||
debug "^4.1.1"
|
||||
denque "^1.1.0"
|
||||
lodash.defaults "^4.2.0"
|
||||
lodash.flatten "^4.4.0"
|
||||
redis-commands "1.5.0"
|
||||
redis-errors "^1.2.0"
|
||||
redis-parser "^3.0.0"
|
||||
standard-as-callback "^2.0.1"
|
||||
|
||||
ipaddr.js@1.8.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.0.tgz#eaa33d6ddd7ace8f7f6fe0c9ca0440e706738b1e"
|
||||
@ -2382,6 +2457,13 @@ is-integer@^1.0.4:
|
||||
dependencies:
|
||||
is-finite "^1.0.0"
|
||||
|
||||
is-nan@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.0.tgz#85d1f5482f7051c2019f5673ccebdb06f3b0db03"
|
||||
integrity sha512-z7bbREymOqt2CCaZVly8aC4ML3Xhfi0ekuOnjO2L8vKdl+CttdVoGZQhd4adMFAsxQ5VeRVwORs4tU8RH+HFtQ==
|
||||
dependencies:
|
||||
define-properties "^1.1.3"
|
||||
|
||||
is-number@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
|
||||
@ -2743,10 +2825,10 @@ leven@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/leven/-/leven-1.0.2.tgz#9144b6eebca5f1d0680169f1a6770dcea60b75c3"
|
||||
integrity sha1-kUS27ryl8dBoAWnxpncNzqYLdcM=
|
||||
|
||||
libflitter@^0.52.0:
|
||||
version "0.52.0"
|
||||
resolved "https://registry.yarnpkg.com/libflitter/-/libflitter-0.52.0.tgz#c9279d5cff93fd6f24b89f46c2add2f0c3e3b0f1"
|
||||
integrity sha512-+YR+rww1i7NREfXPJwoxc0yhpmNbMYozgcAed6yfkxK59JS9DBM7cUWwV+dxG0UlFilOxZBGXdj/oKiIs+huvQ==
|
||||
libflitter@^0.52.1:
|
||||
version "0.52.1"
|
||||
resolved "https://registry.yarnpkg.com/libflitter/-/libflitter-0.52.1.tgz#3025dc97b9baaaaba00907e9c2de9a39cf41f4d8"
|
||||
integrity sha512-USoAxZFfwHZkwd39wP/DuhDN3EnXYFcSby0W2WFuJlwCOXIfFTCr0tNcPbvHJrPI/Cxgy5ngExkzpG0OZhS+yQ==
|
||||
dependencies:
|
||||
colors "^1.3.3"
|
||||
connect-mongodb-session "^2.2.0"
|
||||
@ -2758,7 +2840,7 @@ libflitter@^0.52.0:
|
||||
express-graphql "^0.9.0"
|
||||
express-session "^1.15.6"
|
||||
flitter-di "^0.5.0"
|
||||
flitter-orm "^0.3.1"
|
||||
flitter-orm "^0.4.0"
|
||||
graphql "^14.5.4"
|
||||
http-status "^1.4.2"
|
||||
pug "^2.0.3"
|
||||
@ -2802,6 +2884,16 @@ lodash.clonedeep@4.x:
|
||||
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
||||
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
|
||||
|
||||
lodash.defaults@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
|
||||
integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=
|
||||
|
||||
lodash.flatten@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
|
||||
integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
|
||||
|
||||
lodash.flattendeep@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
|
||||
@ -2827,7 +2919,7 @@ lodash@^3.10.0, lodash@^3.9.3:
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
|
||||
integrity sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=
|
||||
|
||||
lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15:
|
||||
lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15:
|
||||
version "4.17.15"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
||||
@ -3035,11 +3127,23 @@ mocha@^7.0.1, mocha@^7.1.0:
|
||||
yargs-parser "13.1.2"
|
||||
yargs-unparser "1.6.0"
|
||||
|
||||
moment-timezone@^0.5.31:
|
||||
version "0.5.31"
|
||||
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.31.tgz#9c40d8c5026f0c7ab46eda3d63e49c155148de05"
|
||||
integrity sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==
|
||||
dependencies:
|
||||
moment ">= 2.9.0"
|
||||
|
||||
moment@2.15.2:
|
||||
version "2.15.2"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.15.2.tgz#1bfdedf6a6e345f322fe956d5df5bd08a8ce84dc"
|
||||
integrity sha1-G/3t9qbjRfMi/pVtXfW9CKjOhNw=
|
||||
|
||||
"moment@>= 2.9.0":
|
||||
version "2.26.0"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.26.0.tgz#5e1f82c6bafca6e83e808b30c8705eed0dcbd39a"
|
||||
integrity sha512-oIixUO+OamkUkwjhAVE18rAMfRJNsNe/Stid/gwHSOfHrOtw9EhAY2AHvdKZ/k/MggcYELFCJz/Sn2pL8b8JMw==
|
||||
|
||||
moment@^2.10.6, moment@^2.24.0:
|
||||
version "2.24.0"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
|
||||
@ -3240,6 +3344,11 @@ node-preload@^0.2.1:
|
||||
dependencies:
|
||||
process-on-spawn "^1.0.0"
|
||||
|
||||
nodemailer@^6.4.6:
|
||||
version "6.4.6"
|
||||
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.4.6.tgz#d37f504f6560b36616f646a606894fe18819107f"
|
||||
integrity sha512-/kJ+FYVEm2HuUlw87hjSqTss+GU35D4giOpdSfGp7DO+5h6RlJj7R94YaYHOkoxu1CSaM0d3WRBtCzwXrY6MKA==
|
||||
|
||||
nopt@^4.0.1, nopt@~4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
|
||||
@ -3908,6 +4017,23 @@ recursive-readdir@^2.2.2:
|
||||
dependencies:
|
||||
minimatch "3.0.4"
|
||||
|
||||
redis-commands@1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.5.0.tgz#80d2e20698fe688f227127ff9e5164a7dd17e785"
|
||||
integrity sha512-6KxamqpZ468MeQC3bkWmCB1fp56XL64D4Kf0zJSwDZbVLLm7KFkoIcHrgRvQ+sk8dnhySs7+yBg94yIkAK7aJg==
|
||||
|
||||
redis-errors@^1.0.0, redis-errors@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad"
|
||||
integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=
|
||||
|
||||
redis-parser@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4"
|
||||
integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=
|
||||
dependencies:
|
||||
redis-errors "^1.0.0"
|
||||
|
||||
regenerate@^1.2.1:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"
|
||||
@ -4351,6 +4477,11 @@ stable@~0.1.3:
|
||||
resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
|
||||
integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==
|
||||
|
||||
standard-as-callback@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.0.1.tgz#ed8bb25648e15831759b6023bdb87e6b60b38126"
|
||||
integrity sha512-NQOxSeB8gOI5WjSaxjBgog2QFw55FV8TkS6Y07BiB3VJ8xNTvUYm0wl0s8ObgQ5NhdpnNfigMIKjgPESzgr4tg==
|
||||
|
||||
standard-error@1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/standard-error/-/standard-error-1.1.0.tgz#23e5168fa1c0820189e5812701a79058510d0d34"
|
||||
|
Loading…
Reference in New Issue
Block a user