import { HttpStatusCode, IHttp, IHttpResponse, IModify, IPersistence, IRead, } from "@rocket.chat/apps-engine/definition/accessors"; import { ApiEndpoint, IApiEndpointInfo, IApiRequest, IApiResponse, } from "@rocket.chat/apps-engine/definition/api"; import { IApp } from "@rocket.chat/apps-engine/definition/IApp"; import { ConflictError } from "../errors/ConflictError"; import { EmptyRequestError } from "../errors/EmptyRequestError"; import { EmptyResponseError } from "../errors/EmptyResponseError"; import { JsonParseError } from "../errors/JsonParseError"; import { SCIMError, SCIMErrorType } from "../scim/Error"; import { Context } from "./Context"; type ApiEndpointMethod = ( request: IApiRequest, endpoint: IApiEndpointInfo, read: IRead, modify: IModify, http: IHttp, persis: IPersistence, ) => Promise; export interface IScimEndpoint { _get?(ctx: Context): Promise; _post?(ctx: Context): Promise; _put?(ctx: Context): Promise; _delete?(ctx: Context): Promise; } export abstract class ScimEndpoint extends ApiEndpoint { public get: ApiEndpointMethod | undefined; public post: ApiEndpointMethod | undefined; public put: ApiEndpointMethod | undefined; public delete: ApiEndpointMethod | undefined; constructor(app: IApp) { super(app); this.get = this.wrapMethod("get"); this.post = this.wrapMethod("post"); this.put = this.wrapMethod("put"); this.delete = this.wrapMethod("delete"); } protected success(content?: any): IApiResponse { return this.response({ status: HttpStatusCode.OK, content, }); } protected response(response: IApiResponse): IApiResponse { if (response.headers === undefined) { response.headers = {}; } response.headers["Content-Type"] = "application/scim+json"; return response; } protected error(error: SCIMError): IApiResponse { return this.response({ status: parseInt(error.status, 10), content: error, }); } protected parseResponse(response: IHttpResponse): any { if (!response.content) { throw new EmptyResponseError(); } let content: any; try { content = JSON.parse(response.content); } catch (e) { throw new JsonParseError(); } return content; } protected hasContent(request: IApiRequest) { if (!request.content || Object.keys(request.content).length === 0) { throw new EmptyRequestError(); } } protected handleError(o: any) { if (!o.success) { if (o.error?.includes("already in use")) { throw new ConflictError( o.error.includes("@") ? "email" : "username", ); } if (o.error?.includes("not found")) { } throw new Error(o.error); } } private wrapMethod(name: string): ApiEndpointMethod | undefined { const method = this[`_${name}`]; if (method === undefined || typeof method !== "function") { return undefined; } return async ( request: IApiRequest, endpoint: IApiEndpointInfo, read: IRead, modify: IModify, http: IHttp, persis: IPersistence, ): Promise => { try { const ctx = new Context( request, endpoint, read, modify, http, persis, ); await ctx.checkAuth(); return await method.bind(this)(ctx); } catch (e) { let err: SCIMError; if (e.toSCIMError && typeof e.toSCIMError === "function") { err = e.toSCIMError(); } else { err = new SCIMError() .setStatus(HttpStatusCode.INTERNAL_SERVER_ERROR) .setScimType(SCIMErrorType.INVALID_VALUE) .setDetail(e.message); } return this.error(err); } }; } }