move to basic auth
This commit is contained in:
parent
4cc01ed771
commit
0b1a83841d
8 changed files with 39 additions and 85 deletions
44
ScimApp.ts
44
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),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ export class GroupEndpoint extends ScimEndpoint implements IScimEndpoint {
|
|||
const targetIds = new Set<string>(
|
||||
SCIMGroup.fromPlain(ctx.content()).members.map((x) => x.value),
|
||||
);
|
||||
targetIds.add(await ctx.rc.getUserId());
|
||||
targetIds.add(ctx.rc.userId);
|
||||
const currentIds = new Set<string>(
|
||||
membersRaw.members.map((x) => x.user._id),
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -22,7 +22,7 @@ export class UserEndpoint extends ScimEndpoint implements IScimEndpoint {
|
|||
|
||||
public async _put(ctx: Context): Promise<IApiResponse> {
|
||||
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<IApiResponse> {
|
||||
if (ctx.id() === (await ctx.rc.getUserId())) {
|
||||
if (ctx.id() === ctx.rc.userId) {
|
||||
throw new SCIMError()
|
||||
.setStatus(HttpStatusCode.FORBIDDEN)
|
||||
.setScimType(SCIMErrorType.MUTABILITY)
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<string> {
|
||||
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<IHttpRequest> {
|
||||
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);
|
||||
|
|
Reference in a new issue