From 0b1a83841dc3b4e772a79b776e630f702df36c2e Mon Sep 17 00:00:00 2001 From: Hugo Renard Date: Tue, 3 May 2022 14:17:30 +0200 Subject: [PATCH] move to basic auth --- ScimApp.ts | 44 -------------------------------- src/endpoints/Context.ts | 32 ++++++++++------------- src/endpoints/GroupEndpoint.ts | 2 +- src/endpoints/ScimEndpoint.ts | 1 - src/endpoints/UserEndpoint.ts | 4 +-- src/endpoints/common/endpoint.ts | 6 +++++ src/errors/UnauthorizedError.ts | 2 +- src/rc-sdk/RcSdk.ts | 33 +++++++++++------------- 8 files changed, 39 insertions(+), 85 deletions(-) diff --git a/ScimApp.ts b/ScimApp.ts index ba4364f..133e1de 100644 --- a/ScimApp.ts +++ b/ScimApp.ts @@ -9,11 +9,9 @@ import { } from "@rocket.chat/apps-engine/definition/api"; import { App } from "@rocket.chat/apps-engine/definition/App"; import { IAppInfo } from "@rocket.chat/apps-engine/definition/metadata"; -import { SettingType } from "@rocket.chat/apps-engine/definition/settings"; import { GroupEndpoint } from "./src/endpoints/GroupEndpoint"; import { GroupsEndpoint } from "./src/endpoints/GroupsEndpoint"; -import crypto = require("crypto"); import { UserEndpoint } from "./src/endpoints/UserEndpoint"; import { UsersEndpoint } from "./src/endpoints/UsersEndpoint"; @@ -33,47 +31,5 @@ export class ScimApp extends App { new GroupEndpoint(this), ], }); - - configuration.settings.provideSetting({ - id: "rc-user-id", - type: SettingType.STRING, - packageValue: "", - required: true, - public: false, - i18nLabel: "Rocket.Chat User ID", - }); - - configuration.settings.provideSetting({ - id: "rc-token", - type: SettingType.STRING, - packageValue: "", - required: true, - public: false, - i18nLabel: "Rocket.Chat Token", - }); - - configuration.settings.provideSetting({ - id: "auth-mode", - type: SettingType.SELECT, - packageValue: "", - required: true, - public: false, - i18nLabel: "Auth mode for the SCIM endpoints.", - value: "bearer", - values: [ - { key: "bearer", i18nLabel: "Bearer token" }, - { key: "none", i18nLabel: "None" }, - ], - }); - - configuration.settings.provideSetting({ - id: "auth-bearer", - type: SettingType.STRING, - packageValue: "", - required: true, - public: false, - i18nLabel: "A bearer token to access the SCIM endpoints.", - value: crypto.randomBytes(128).toString("base64").slice(0, 128), - }); } } diff --git a/src/endpoints/Context.ts b/src/endpoints/Context.ts index 251c561..7ff46f0 100644 --- a/src/endpoints/Context.ts +++ b/src/endpoints/Context.ts @@ -9,6 +9,7 @@ import { IApiEndpointInfo, IApiRequest, } from "@rocket.chat/apps-engine/definition/api"; +import { Buffer } from "buffer"; import { EmptyRequestError } from "../errors/EmptyRequestError"; import { UnauthorizedError } from "../errors/UnauthorizedError"; import { RcSdk } from "../rc-sdk/RcSdk"; @@ -33,8 +34,6 @@ export class Context { persis: IPersistence, log: ILogger, ) { - this.rc = new RcSdk(http, read, log); - this.store = new Store(read, persis); this.request = request; this.endpoint = endpoint; this.read = read; @@ -42,6 +41,19 @@ export class Context { this.http = http; this.persis = persis; this.log = log; + this.store = new Store(read, persis); + + try { + const value = this.request.headers.authorization.replace( + "Basic ", + "", + ); + const decodedValue = Buffer.from(value, "base64").toString("utf-8"); + const [username, password] = decodedValue.split(":"); + this.rc = new RcSdk(username, password, http, log); + } catch { + throw new UnauthorizedError(); + } } public id(): string { @@ -57,20 +69,4 @@ export class Context { } return this.request.content; } - - public async checkAuth() { - const authMode = await this.read - .getEnvironmentReader() - .getSettings() - .getValueById("auth-mode"); - if (authMode === "bearer") { - const token = await this.read - .getEnvironmentReader() - .getSettings() - .getValueById("auth-bearer"); - if (this.request.headers.authorization !== `Bearer ${token}`) { - throw new UnauthorizedError(); - } - } - } } diff --git a/src/endpoints/GroupEndpoint.ts b/src/endpoints/GroupEndpoint.ts index 07316eb..1f5ea09 100644 --- a/src/endpoints/GroupEndpoint.ts +++ b/src/endpoints/GroupEndpoint.ts @@ -44,7 +44,7 @@ export class GroupEndpoint extends ScimEndpoint implements IScimEndpoint { const targetIds = new Set( SCIMGroup.fromPlain(ctx.content()).members.map((x) => x.value), ); - targetIds.add(await ctx.rc.getUserId()); + targetIds.add(ctx.rc.userId); const currentIds = new Set( membersRaw.members.map((x) => x.user._id), ); diff --git a/src/endpoints/ScimEndpoint.ts b/src/endpoints/ScimEndpoint.ts index f5f1bbf..4697ad5 100644 --- a/src/endpoints/ScimEndpoint.ts +++ b/src/endpoints/ScimEndpoint.ts @@ -117,7 +117,6 @@ export abstract class ScimEndpoint extends ApiEndpoint { persis, logger, ); - await ctx.checkAuth(); logger.debug( `SCIM Request ${name.toUpperCase()} /${this.path}`, request.content, diff --git a/src/endpoints/UserEndpoint.ts b/src/endpoints/UserEndpoint.ts index 58bcdbb..be61c09 100644 --- a/src/endpoints/UserEndpoint.ts +++ b/src/endpoints/UserEndpoint.ts @@ -22,7 +22,7 @@ export class UserEndpoint extends ScimEndpoint implements IScimEndpoint { public async _put(ctx: Context): Promise { const u = SCIMUser.fromPlain(ctx.content()); - if (ctx.id() === (await ctx.rc.getUserId())) { + if (ctx.id() === ctx.rc.userId) { u.active = true; if (!u.roles.find((x) => x.value === "admin")) { u.roles.push({ value: "admin" }); @@ -51,7 +51,7 @@ export class UserEndpoint extends ScimEndpoint implements IScimEndpoint { } public async _delete(ctx: Context): Promise { - if (ctx.id() === (await ctx.rc.getUserId())) { + if (ctx.id() === ctx.rc.userId) { throw new SCIMError() .setStatus(HttpStatusCode.FORBIDDEN) .setScimType(SCIMErrorType.MUTABILITY) diff --git a/src/endpoints/common/endpoint.ts b/src/endpoints/common/endpoint.ts index 2823515..a618b0a 100644 --- a/src/endpoints/common/endpoint.ts +++ b/src/endpoints/common/endpoint.ts @@ -1,7 +1,13 @@ import { ConflictError } from "../../errors/ConflictError"; +import { UnauthorizedError } from "../../errors/UnauthorizedError"; export function handleRcError(o: any) { if (!o.success) { + if (o.status === "error") { + if (o.message === "You must be logged in to do this.") { + throw new UnauthorizedError(o.error); + } + } if (o.error?.includes("already in use")) { throw new ConflictError( o.error.includes("@") ? "email" : "username", diff --git a/src/errors/UnauthorizedError.ts b/src/errors/UnauthorizedError.ts index 6e2495e..a773bf6 100644 --- a/src/errors/UnauthorizedError.ts +++ b/src/errors/UnauthorizedError.ts @@ -3,7 +3,7 @@ import { SCIMError, SCIMErrorType } from "../scim/Error"; import { BaseError } from "./BaseError"; export class UnauthorizedError extends BaseError { - public message = "The bearer token is missing or doesn't match."; + public message = "The username or password is missing or incorrect."; public toSCIMError(): SCIMError { return new SCIMError() .setStatus(HttpStatusCode.UNAUTHORIZED) diff --git a/src/rc-sdk/RcSdk.ts b/src/rc-sdk/RcSdk.ts index b3e8d5e..fa852a0 100644 --- a/src/rc-sdk/RcSdk.ts +++ b/src/rc-sdk/RcSdk.ts @@ -20,14 +20,25 @@ export class RcSdk { public user: RcSdkUser; public team: RcSdkTeam; public role: RcSdkRole; + public readonly userId: string; private readonly baseUrl = "http://localhost:3000/api/v1"; private readonly http: IHttp; - private readonly read: IRead; private readonly logger: ILogger; + private readonly headers: any; - constructor(http: IHttp, read: IRead, logger: ILogger) { + constructor( + username: string, + password: string, + http: IHttp, + logger: ILogger, + ) { + this.userId = username; + this.headers = { + "X-User-Id": username, + "X-Auth-Token": password, + "Content-Type": "application/json", + }; this.http = http; - this.read = read; this.logger = logger; this.user = new RcSdkUser(this); this.team = new RcSdkTeam(this); @@ -67,27 +78,13 @@ export class RcSdk { return content; } - public async getUserId(): Promise { - return await this.read - .getEnvironmentReader() - .getSettings() - .getValueById("rc-user-id"); - } - private buildUrl(url: string): string { return `${this.baseUrl}/${url}`; } private async buildOptions(content?: any): Promise { const options: IHttpRequest = { - headers: { - "X-User-Id": await this.getUserId(), - "X-Auth-Token": await this.read - .getEnvironmentReader() - .getSettings() - .getValueById("rc-token"), - "Content-Type": "application/json", - }, + headers: this.headers, }; if (content !== undefined) { options.content = JSON.stringify(content);