From d9637b1d3d8c24a8767884c539d650242194633a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sun, 30 Jun 2019 19:38:24 -0600 Subject: [PATCH] Very simple terms of service auth requirement --- src/api/controllers/TermsController.ts | 35 ++++++++++++++++++++++++++ src/api/msc/MSCTermsService.ts | 25 ++++++++++++++++++ src/api/security/MSCSecurity.ts | 35 +++++++++++++++++++++++--- 3 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 src/api/controllers/TermsController.ts create mode 100644 src/api/msc/MSCTermsService.ts diff --git a/src/api/controllers/TermsController.ts b/src/api/controllers/TermsController.ts new file mode 100644 index 0000000..ebe7781 --- /dev/null +++ b/src/api/controllers/TermsController.ts @@ -0,0 +1,35 @@ +import { AutoWired } from "typescript-ioc/es6"; +import { IMSCUser } from "../security/MSCSecurity"; + +export interface ILanguagePolicy { + name: string; + url: string; +} + +export interface IPolicy { + version: string; + + // a string value is not allowed here, but TypeScript is angry otherwise. + [language: string]: string | ILanguagePolicy; +} + +export interface ITermsNotSignedResponse { + policies: { [policyName: string]: IPolicy }; +} + +/** + * API controller for terms of service management + */ +@AutoWired +export default class TermsController { + constructor() { + } + + public async doesUserNeedToSignTerms(user: IMSCUser): Promise { + return Object.keys((await this.getMissingTermsForUser(user)).policies).length > 0; + } + + public async getMissingTermsForUser(_user: IMSCUser): Promise { + return {policies: {}}; + } +} \ No newline at end of file diff --git a/src/api/msc/MSCTermsService.ts b/src/api/msc/MSCTermsService.ts new file mode 100644 index 0000000..cc9405b --- /dev/null +++ b/src/api/msc/MSCTermsService.ts @@ -0,0 +1,25 @@ +import { Context, GET, Path, Security, ServiceContext } from "typescript-rest"; +import { AutoWired, Inject } from "typescript-ioc/es6"; +import { ROLE_MSC_USER } from "../security/MSCSecurity"; +import TermsController, { ITermsNotSignedResponse } from "../controllers/TermsController"; + +/** + * API for account management + */ +@Path("/_matrix/integrations/v1/terms") +@AutoWired +export class MSCTermsService { + + @Inject + private termsController: TermsController; + + @Context + private context: ServiceContext; + + @GET + @Path("") + @Security(ROLE_MSC_USER) + public async needsSignatures(): Promise { + return this.termsController.getMissingTermsForUser(this.context.request.user); + } +} \ No newline at end of file diff --git a/src/api/security/MSCSecurity.ts b/src/api/security/MSCSecurity.ts index ec03404..dac9f16 100644 --- a/src/api/security/MSCSecurity.ts +++ b/src/api/security/MSCSecurity.ts @@ -3,6 +3,7 @@ import { Request, RequestHandler, Response, Router } from "express"; import { ApiError } from "../ApiError"; import { LogService } from "matrix-js-snippets"; import AccountController from "../controllers/AccountController"; +import TermsController from "../controllers/TermsController"; export interface IMSCUser { userId: string; @@ -10,18 +11,25 @@ export interface IMSCUser { } export const ROLE_MSC_USER = "ROLE_MSC_USER"; -export const ROLE_MSC_TERMS_SIGNED = "ROLE_MSC_TERMS_SIGNED"; + +const TERMS_IGNORED_ROUTES = [ + {method: "GET", path: "/_matrix/integrations/v1/terms"}, + {method: "POST", path: "/_matrix/integrations/v1/terms"}, + {method: "POST", path: "/_matrix/integrations/v1/register"}, + {method: "POST", path: "/_matrix/integrations/v1/logout"}, +]; export default class MSCSecurity implements ServiceAuthenticator { private accountController = new AccountController(); + private termsController = new TermsController(); public getRoles(req: Request): string[] { if (req.user) return [ROLE_MSC_USER]; return []; } - getMiddleware(): RequestHandler { + public getMiddleware(): RequestHandler { return (async (req: Request, res: Response, next: () => void) => { try { let token = null; @@ -41,6 +49,27 @@ export default class MSCSecurity implements ServiceAuthenticator { userId: await this.accountController.getTokenOwner(token), token: token, }; + + let needTerms = true; + if (req.method !== "OPTIONS") { + for (const route of TERMS_IGNORED_ROUTES) { + if (route.method === req.method && route.path === req.path) { + needTerms = false; + break; + } + } + } else needTerms = false; + + if (needTerms) { + const signatureNeeded = await this.termsController.doesUserNeedToSignTerms(req.user); + if (signatureNeeded) { + return res.status(403).json({ + errcode: "M_TERMS_NOT_SIGNED", + error: "The user has not accepted all terms of service for this integration manager", + }); + } + } + return next(); } else { return res.status(401).json({errcode: "M_INVALID_TOKEN", error: "Invalid token"}); @@ -57,7 +86,7 @@ export default class MSCSecurity implements ServiceAuthenticator { }); } - initialize(_router: Router): void { + public initialize(_router: Router): void { } } \ No newline at end of file