Add foreign IP login notifications
This commit is contained in:
parent
8dd3accfc4
commit
d29e6f057a
@ -3,4 +3,3 @@
|
|||||||
- show app documentation somewhere besides final page of setup
|
- show app documentation somewhere besides final page of setup
|
||||||
- Logins as jobs?
|
- Logins as jobs?
|
||||||
- OpenID Connect - oidc-provider
|
- OpenID Connect - oidc-provider
|
||||||
- Login from new IP notifications
|
|
@ -4,7 +4,7 @@ const email_validator = require('email-validator')
|
|||||||
|
|
||||||
class AuthController extends Controller {
|
class AuthController extends Controller {
|
||||||
static get services() {
|
static get services() {
|
||||||
return [...super.services, 'models', 'auth', 'MFA', 'output', 'configs', 'utility']
|
return [...super.services, 'models', 'auth', 'MFA', 'output', 'configs', 'utility', 'activity']
|
||||||
}
|
}
|
||||||
|
|
||||||
async get_auth_user(req, res, next) {
|
async get_auth_user(req, res, next) {
|
||||||
@ -168,7 +168,6 @@ class AuthController extends Controller {
|
|||||||
return res.api(data)
|
return res.api(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async get_roles(req, res, next) {
|
async get_roles(req, res, next) {
|
||||||
const role_config = this.configs.get('auth.roles')
|
const role_config = this.configs.get('auth.roles')
|
||||||
const data = []
|
const data = []
|
||||||
@ -603,6 +602,9 @@ class AuthController extends Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a login tracking activity
|
||||||
|
await this.activity.login(req)
|
||||||
|
|
||||||
return res.api({
|
return res.api({
|
||||||
success: true,
|
success: true,
|
||||||
session_created: !!req.body.create_session,
|
session_created: !!req.body.create_session,
|
||||||
|
37
app/jobs/ForeignIPLoginAlert.job.js
Normal file
37
app/jobs/ForeignIPLoginAlert.job.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
const { Job } = require('flitter-jobs')
|
||||||
|
|
||||||
|
class ForeignIPLoginAlertJob extends Job {
|
||||||
|
static get services() { return [...super.services, 'models', 'jobs', 'output', 'configs'] }
|
||||||
|
|
||||||
|
async execute(job) {
|
||||||
|
const { data } = job
|
||||||
|
const { user_id, ip } = data
|
||||||
|
|
||||||
|
try {
|
||||||
|
const User = this.models.get('auth:User')
|
||||||
|
const user = await User.findById(user_id)
|
||||||
|
if ( !user ) throw new Error('Unable to find user with ID: '+user_id)
|
||||||
|
|
||||||
|
this.output.info('Sending foreign IP login alert to user.')
|
||||||
|
|
||||||
|
await this.jobs.queue('mailer').add('EMail', {
|
||||||
|
to: user.email,
|
||||||
|
subject: `Security Alert | ${this.configs.get('app.name')}`,
|
||||||
|
email_params: {
|
||||||
|
header_text: 'Login From New IP',
|
||||||
|
body_paragraphs: [
|
||||||
|
`We've detected a login to your ${this.configs.get('app.name')} account from a new IP address (${ip}).`,
|
||||||
|
'If this was you, no further action is required. If this was not you, please log into your account and reset your password.',
|
||||||
|
'Also, consider enabling multi-factor authentication to protect your account.',
|
||||||
|
],
|
||||||
|
button_text: 'Account Settings',
|
||||||
|
button_link: `${this.configs.get('app.url')}dash/profile`,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
this.output.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = exports = ForeignIPLoginAlertJob
|
15
app/models/Activity.model.js
Normal file
15
app/models/Activity.model.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
const { Model } = require('flitter-orm')
|
||||||
|
|
||||||
|
class ActivityModel extends Model {
|
||||||
|
static get schema() {
|
||||||
|
return {
|
||||||
|
user_id: String,
|
||||||
|
session_id: String,
|
||||||
|
action: String,
|
||||||
|
timestamp: { type: Date, default: () => new Date },
|
||||||
|
metadata: Object,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = exports = ActivityModel
|
45
app/services/activity.service.js
Normal file
45
app/services/activity.service.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
const { Service } = require('flitter-di')
|
||||||
|
|
||||||
|
class ActivityService extends Service {
|
||||||
|
static get services() { return ['models', 'jobs'] }
|
||||||
|
|
||||||
|
model() {
|
||||||
|
return this.models.get('Activity')
|
||||||
|
}
|
||||||
|
|
||||||
|
async login(req) {
|
||||||
|
const Activity = this.model()
|
||||||
|
const activity = new Activity({
|
||||||
|
user_id: req.session.auth.user_id,
|
||||||
|
session_id: req.session.id,
|
||||||
|
action: 'login',
|
||||||
|
metadata: {
|
||||||
|
ip: req.ip
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// If this is a new IP login, send an e-mail alert
|
||||||
|
const foreign_ip = await this.foreign_login_ip(req.session.auth.user_id, req.ip)
|
||||||
|
if ( foreign_ip ) {
|
||||||
|
await this.jobs.queue('notifications').add('ForeignIPLoginAlert', {
|
||||||
|
ip: req.ip,
|
||||||
|
user_id: req.session.auth.user_id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await activity.save()
|
||||||
|
}
|
||||||
|
|
||||||
|
async foreign_login_ip(user_id, ip) {
|
||||||
|
const Activity = this.model()
|
||||||
|
const existing_ip = await Activity.findOne({
|
||||||
|
user_id,
|
||||||
|
action: 'login',
|
||||||
|
'metadata.ip': ip,
|
||||||
|
})
|
||||||
|
|
||||||
|
return !existing_ip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = exports = ActivityService
|
@ -10,6 +10,8 @@ class SettingsUnit extends Unit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async go(app) {
|
async go(app) {
|
||||||
|
app.express.set('trust proxy', true)
|
||||||
|
|
||||||
const Setting = this.models.get('Setting')
|
const Setting = this.models.get('Setting')
|
||||||
const default_settings = this.configs.get('setting.settings')
|
const default_settings = this.configs.get('setting.settings')
|
||||||
for ( const key in default_settings ) {
|
for ( const key in default_settings ) {
|
||||||
|
@ -14,6 +14,7 @@ const jobs_config = {
|
|||||||
queues: [
|
queues: [
|
||||||
'mailer',
|
'mailer',
|
||||||
'password_resets',
|
'password_resets',
|
||||||
|
'notifications',
|
||||||
],
|
],
|
||||||
|
|
||||||
// Mapping of worker name => worker config
|
// Mapping of worker name => worker config
|
||||||
@ -22,7 +23,7 @@ const jobs_config = {
|
|||||||
// The name of the worker is "main"
|
// The name of the worker is "main"
|
||||||
main: {
|
main: {
|
||||||
// This worker will process these queues
|
// This worker will process these queues
|
||||||
queues: ['mailer', 'password_resets'],
|
queues: ['mailer', 'password_resets', 'notifications'],
|
||||||
},
|
},
|
||||||
|
|
||||||
// You can have many workers, and multiple workers can
|
// You can have many workers, and multiple workers can
|
||||||
|
Loading…
Reference in New Issue
Block a user