chore: Flag users with platform used

This commit is contained in:
Tom Moor
2022-09-18 17:53:55 -04:00
parent ae697339ac
commit f8912732b8
6 changed files with 32 additions and 13 deletions

View File

@@ -123,7 +123,7 @@ export default function auth(options: AuthenticationOptions = {}) {
} }
// not awaiting the promise here so that the request is not blocked // not awaiting the promise here so that the request is not blocked
user.updateActiveAt(ctx.request.ip).catch((err) => { user.updateActiveAt(ctx).catch((err) => {
Logger.error("Failed to update user activeAt", err); Logger.error("Failed to update user activeAt", err);
}); });

View File

@@ -10,8 +10,8 @@ import {
BeforeCreate, BeforeCreate,
} from "sequelize-typescript"; } from "sequelize-typescript";
import { TeamValidation } from "@shared/validations"; import { TeamValidation } from "@shared/validations";
import env from "@server/env";
import { ValidationError } from "@server/errors"; import { ValidationError } from "@server/errors";
import isCloudHosted from "~/utils/isCloudHosted";
import Team from "./Team"; import Team from "./Team";
import User from "./User"; import User from "./User";
import IdModel from "./base/IdModel"; import IdModel from "./base/IdModel";
@@ -19,6 +19,8 @@ import Fix from "./decorators/Fix";
import IsFQDN from "./validators/IsFQDN"; import IsFQDN from "./validators/IsFQDN";
import Length from "./validators/Length"; import Length from "./validators/Length";
const isCloudHosted = env.DEPLOYMENT === "hosted";
@Table({ tableName: "team_domains", modelName: "team_domain" }) @Table({ tableName: "team_domains", modelName: "team_domain" })
@Fix @Fix
class TeamDomain extends IdModel { class TeamDomain extends IdModel {

View File

@@ -1,6 +1,7 @@
import crypto from "crypto"; import crypto from "crypto";
import { addMinutes, subMinutes } from "date-fns"; import { addMinutes, subMinutes } from "date-fns";
import JWT from "jsonwebtoken"; import JWT from "jsonwebtoken";
import { Context } from "koa";
import { Transaction, QueryTypes, SaveOptions, Op } from "sequelize"; import { Transaction, QueryTypes, SaveOptions, Op } from "sequelize";
import { import {
Table, Table,
@@ -51,6 +52,8 @@ import NotContainsUrl from "./validators/NotContainsUrl";
export enum UserFlag { export enum UserFlag {
InviteSent = "inviteSent", InviteSent = "inviteSent",
InviteReminderSent = "inviteReminderSent", InviteReminderSent = "inviteReminderSent",
DesktopWeb = "desktopWeb",
MobileWeb = "mobileWeb",
} }
export enum UserRole { export enum UserRole {
@@ -264,8 +267,11 @@ class User extends ParanoidModel {
if (!this.flags) { if (!this.flags) {
this.flags = {}; this.flags = {};
} }
this.flags[flag] = value ? 1 : 0; const binary = value ? 1 : 0;
this.changed("flags", true); if (this.flags[flag] !== binary) {
this.flags[flag] = binary;
this.changed("flags", true);
}
return this.flags; return this.flags;
}; };
@@ -350,7 +356,8 @@ class User extends ParanoidModel {
.map((c) => c.id); .map((c) => c.id);
}; };
updateActiveAt = async (ip: string, force = false) => { updateActiveAt = async (ctx: Context, force = false) => {
const { ip } = ctx.request;
const fiveMinutesAgo = subMinutes(new Date(), 5); const fiveMinutesAgo = subMinutes(new Date(), 5);
// ensure this is updated only every few minutes otherwise // ensure this is updated only every few minutes otherwise
@@ -358,13 +365,20 @@ class User extends ParanoidModel {
if (!this.lastActiveAt || this.lastActiveAt < fiveMinutesAgo || force) { if (!this.lastActiveAt || this.lastActiveAt < fiveMinutesAgo || force) {
this.lastActiveAt = new Date(); this.lastActiveAt = new Date();
this.lastActiveIp = ip; this.lastActiveIp = ip;
return this.save({
hooks: false,
});
} }
return this; // Track the clients each user is using
if (ctx.userAgent.isMobile) {
this.setFlag(UserFlag.MobileWeb);
}
if (ctx.userAgent.isDesktop) {
this.setFlag(UserFlag.DesktopWeb);
}
// Save only writes to the database if there are changes
return this.save({
hooks: false,
});
}; };
updateSignedIn = (ip: string) => { updateSignedIn = (ip: string) => {

View File

@@ -45,7 +45,7 @@ router.post("developer.create_test_users", dev(), auth(), async (ctx) => {
// Convert from invites to active users by marking as active // Convert from invites to active users by marking as active
await Promise.all( await Promise.all(
response.users.map((user) => user.updateActiveAt(ctx.request.ip, true)) response.users.map((user) => user.updateActiveAt(ctx, true))
); );
ctx.body = { ctx.body = {

View File

@@ -1,6 +1,7 @@
import Koa, { DefaultContext, DefaultState } from "koa"; import Koa, { BaseContext, DefaultContext, DefaultState } from "koa";
import bodyParser from "koa-body"; import bodyParser from "koa-body";
import Router from "koa-router"; import Router from "koa-router";
import userAgent, { UserAgentContext } from "koa-useragent";
import env from "@server/env"; import env from "@server/env";
import { NotFoundError } from "@server/errors"; import { NotFoundError } from "@server/errors";
import errorHandling from "@server/middlewares/errorHandling"; import errorHandling from "@server/middlewares/errorHandling";
@@ -50,6 +51,7 @@ api.use(
}, },
}) })
); );
api.use<BaseContext, UserAgentContext>(userAgent);
api.use(methodOverride()); api.use(methodOverride());
api.use(apiWrapper()); api.use(apiWrapper());
api.use(editor()); api.use(editor());

View File

@@ -31,7 +31,8 @@ router.get("/redirect", auth(), async (ctx) => {
} }
// ensure that the lastActiveAt on user is updated to prevent replay requests // ensure that the lastActiveAt on user is updated to prevent replay requests
await user.updateActiveAt(ctx.request.ip, true); await user.updateActiveAt(ctx, true);
ctx.cookies.set("accessToken", jwtToken, { ctx.cookies.set("accessToken", jwtToken, {
httpOnly: false, httpOnly: false,
expires: addMonths(new Date(), 3), expires: addMonths(new Date(), 3),