Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions src/Bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
MatrixError,
RoomEvent,
PLManager,
UserID,
} from "matrix-bot-sdk";
import BotUsersManager from "./managers/BotUsersManager";
import { BridgeConfig, BridgePermissionLevel } from "./config/Config";
Expand Down Expand Up @@ -109,6 +110,7 @@
import { OAuthRequest, OAuthRequestResult } from "./tokens/Oauth";
import { IJsonType } from "matrix-bot-sdk/lib/helpers/Types";
import { GitLabInstance } from "./config/sections";
import { ApiError, ErrCode } from "./api";

const log = new Logger("Bridge");

Expand Down Expand Up @@ -188,6 +190,7 @@
this.config.github.auth.id,
await fs.readFile(this.config.github.auth.privateKeyFile, "utf-8"),
this.config.github.baseUrl,
this.tokenStore,
);
await this.github.start();
}
Expand Down Expand Up @@ -245,6 +248,58 @@
Metrics.matrixAppserviceDecryptionFailed.inc();
});

this.as.on(
"previewUrl.query",
(
{ url, userId }: { url: string; userId?: string },
callback: (statusCode: number, data: any) => void,

Check warning on line 255 in src/Bridge.ts

View workflow job for this annotation

GitHub Actions / lint-node

Unexpected any. Specify a different type
) => {
log.debug(`Got request to preview ${url} (for ${userId}`);
if (!userId) {
return callback(
400,
new ApiError(
"user_id value must be provided",
ErrCode.ForbiddenUser,
).jsonBody,
);
}

if (url.startsWith("https://github.com")) {

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High

'
https://github.com
' may be followed by an arbitrary host name.
if (!this.github) {
return callback(
400,
new ApiError(
"Service is not configured with GitHub",
ErrCode.DisabledFeature,
).jsonBody,
);
}
return this.github
.handleURLPreview(new URL(url), new UserID(userId))
.then((response) => {
callback(200, response);
})
.catch((ex) => {
log.warn(`Failed to preview ${url} (for ${userId})`, ex);
if (ex instanceof ApiError) {
callback(ex.statusCode, ex.jsonBody);
} else {
return callback(
500,
new ApiError("Unknown error", ErrCode.Unknown).jsonBody,
);
}
});
} else {
return callback(
404,
new ApiError("Unsupported URL", ErrCode.NotFound).jsonBody,
);
}
},
);

this.queue.subscribe("response.matrix.message");
this.queue.subscribe("notifications.user.events");
this.queue.subscribe("github.*");
Expand Down Expand Up @@ -1484,7 +1539,7 @@
);
// This might be a reply to a notification
try {
const evContent = replyEvent.content as any;

Check warning on line 1542 in src/Bridge.ts

View workflow job for this annotation

GitHub Actions / lint-node

Unexpected any. Specify a different type
const splitParts: string[] =
evContent["uk.half-shot.matrix-hookshot.github.repo"]?.name.split(
"/",
Expand Down
53 changes: 53 additions & 0 deletions src/github/GithubInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import {
} from "./Types";
import axios from "axios";
import UserAgent from "../UserAgent";
import { UserID } from "matrix-bot-sdk";
import { UserTokenStore } from "../tokens/UserTokenStore";
import { ApiError, ErrCode } from "../api";

const log = new Logger("GithubInstance");

Expand Down Expand Up @@ -66,6 +69,7 @@ export class GithubInstance {
private readonly appId: number | string,
private readonly privateKey: string,
private readonly baseUrl: URL,
private readonly tokenStore: UserTokenStore,
) {
this.appId = parseInt(appId as string, 10);
}
Expand Down Expand Up @@ -283,6 +287,55 @@ export class GithubInstance {
`login/oauth/${action}?${q}`
);
}

public async handleURLPreview(url: URL, userId: UserID): Promise<any> {
// Try to get some info for the user.
// TODO: Fallback to public access?
const octokit = await this.tokenStore.getOctokitForUser(userId.toString());
if (!octokit) {
throw new ApiError(
"User is not authenticated with GitHub",
ErrCode.ForbiddenUser,
);
}
// Attempt to parse what the URL is about..
const [_, owner, repo, type, number] = url.pathname.split("/");
if (owner && repo) {
if (type === "pull") {
const pull = await octokit.pulls.get({
owner,
repo,
pull_number: parseInt(number, 10),
});
if (pull.status === 200) {
return {
"og:title": `${pull.data.base.repo.name} | ${pull.data.title} by @${pull.data.user.login}`,
"og:description": pull.data.body,
};
} else {
throw new ApiError(
`Could not access pull request, status ${pull.status}`,
ErrCode.NotFound,
);
}
} else if (type === "issues") {
throw new ApiError(
`Could not access issue request, not implemented`,
ErrCode.NotFound,
);
} else {
throw new ApiError(
`URL not processable, unknown type ${type}`,
ErrCode.NotFound,
);
}
} else {
throw new ApiError(
`URL not processable, must be in the format of org/repo`,
ErrCode.NotFound,
);
}
}
}

export class GithubGraphQLClient {
Expand Down
Loading