Track recently active and signin times (#663)

* Track recently active and signin times

* Trust proxy headers in production
This commit is contained in:
Tom Moor
2018-06-04 19:07:56 -07:00
committed by GitHub
parent 1977278426
commit 53a0f423c3
6 changed files with 62 additions and 0 deletions

View File

@ -86,6 +86,9 @@ router.get('google.callback', async ctx => {
await team.createFirstCollection(user.id);
}
// not awaiting the promise here so that the request is not blocked
user.updateSignedIn(ctx.request.ip);
ctx.cookies.set('lastSignedIn', 'google', {
httpOnly: false,
expires: new Date('2100'),

View File

@ -62,6 +62,9 @@ router.get('slack.callback', async ctx => {
await team.createFirstCollection(user.id);
}
// not awaiting the promise here so that the request is not blocked
user.updateSignedIn(ctx.request.ip);
ctx.cookies.set('lastSignedIn', 'slack', {
httpOnly: false,
expires: new Date('2100'),

View File

@ -70,6 +70,10 @@ if (process.env.NODE_ENV === 'development') {
app.use(mount('/emails', emails));
} else if (process.env.NODE_ENV === 'production') {
// trust header fields set by our proxy. eg X-Forwarded-For
app.proxy = true;
// catch errors in one place, automatically set status and response headers
onerror(app);
if (process.env.BUGSNAG_KEY) {

View File

@ -79,6 +79,9 @@ export default function auth(options?: { required?: boolean } = {}) {
throw new UserSuspendedError({ adminEmail: suspendingAdmin.email });
}
// not awaiting the promise here so that the request is not blocked
user.updateActiveAt(ctx.request.ip);
ctx.state.token = token;
ctx.state.user = user;
// $FlowFixMe

View File

@ -0,0 +1,26 @@
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.addColumn('users', 'lastActiveAt', {
type: Sequelize.DATE,
allowNull: true
});
await queryInterface.addColumn('users', 'lastActiveIp', {
type: Sequelize.STRING,
allowNull: true
});
await queryInterface.addColumn('users', 'lastSignedInAt', {
type: Sequelize.DATE,
allowNull: true
});
await queryInterface.addColumn('users', 'lastSignedInIp', {
type: Sequelize.STRING,
allowNull: true
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.removeColumn('users', 'lastActiveAt');
await queryInterface.removeColumn('users', 'lastActiveIp');
await queryInterface.removeColumn('users', 'lastSignedInAt');
await queryInterface.removeColumn('users', 'lastSignedInIp');
}
}

View File

@ -3,6 +3,7 @@ import crypto from 'crypto';
import bcrypt from 'bcrypt';
import uuid from 'uuid';
import JWT from 'jsonwebtoken';
import subMinutes from 'date-fns/sub_minutes';
import { DataTypes, sequelize, encryptedFields } from '../sequelize';
import { publicS3Endpoint, uploadToS3FromUrl } from '../utils/s3';
import { sendEmail } from '../mailer';
@ -28,6 +29,10 @@ const User = sequelize.define(
serviceId: { type: DataTypes.STRING, allowNull: true, unique: true },
slackData: DataTypes.JSONB,
jwtSecret: encryptedFields.vault('jwtSecret'),
lastActiveAt: DataTypes.DATE,
lastActiveIp: DataTypes.STRING,
lastSignedInAt: DataTypes.DATE,
lastSignedInIp: DataTypes.STRING,
suspendedAt: DataTypes.DATE,
suspendedById: DataTypes.UUID,
},
@ -53,6 +58,24 @@ User.associate = models => {
};
// Instance methods
User.prototype.updateActiveAt = function(ip) {
const fiveMinutesAgo = subMinutes(new Date(), 5);
// ensure this is updated only every few minutes otherwise
// we'll be constantly writing to the DB as API requests happen
if (this.lastActiveAt < fiveMinutesAgo) {
this.lastActiveAt = new Date();
this.lastActiveIp = ip;
return this.save({ hooks: false });
}
};
User.prototype.updateSignedIn = function(ip) {
this.lastSignedInAt = new Date();
this.lastSignedInIp = ip;
return this.save({ hooks: false });
};
User.prototype.getJwtToken = function() {
return JWT.sign({ id: this.id }, this.jwtSecret);
};