diff --git a/src/Connections/GitlabIssue.ts b/src/Connections/GitlabIssue.ts index 99173156..947e639c 100644 --- a/src/Connections/GitlabIssue.ts +++ b/src/Connections/GitlabIssue.ts @@ -179,8 +179,10 @@ export class GitLabIssueConnection implements IConnection { if (ev.content.body === '!sync') { // Sync data. // return this.syncIssueState(); + return true; } await this.onMatrixIssueComment(ev); + return true; } public toString() { diff --git a/src/Connections/IConnection.ts b/src/Connections/IConnection.ts index d8579b81..93516477 100644 --- a/src/Connections/IConnection.ts +++ b/src/Connections/IConnection.ts @@ -13,9 +13,10 @@ export interface IConnection { onEvent?: (ev: MatrixEvent) => Promise; /** - * When a room gets a message event + * When a room gets a message event. + * @returns Was the message handled */ - onMessageEvent?: (ev: MatrixEvent) => Promise; + onMessageEvent?: (ev: MatrixEvent) => Promise; onIssueCreated?: (ev: IssuesOpenedEvent) => Promise; diff --git a/src/Connections/JiraProject.ts b/src/Connections/JiraProject.ts index d349d716..fa50b8cd 100644 --- a/src/Connections/JiraProject.ts +++ b/src/Connections/JiraProject.ts @@ -7,13 +7,13 @@ import { JiraIssueEvent, JiraIssueUpdatedEvent } from "../Jira/WebhookTypes"; import { FormatUtil } from "../FormatUtil"; import markdownit from "markdown-it"; import { generateJiraWebLinkFromIssue } from "../Jira"; -import { JiraIssue, JiraProject } from "../Jira/Types"; +import { JiraProject } from "../Jira/Types"; import { botCommand, BotCommands, compileBotCommands } from "../BotCommands"; import { MatrixMessageContent } from "../MatrixEvent"; import { CommandConnection } from "./CommandConnection"; -import { start } from "repl"; import { UserTokenStore } from "../UserTokenStore"; import { CommandError, NotLoggedInError } from "../errors"; +import JiraApi from "jira-client"; type JiraAllowedEventsNames = "issue.created"; const JiraAllowedEvents: JiraAllowedEventsNames[] = ["issue.created"]; @@ -159,11 +159,14 @@ export class JiraProjectConnection extends CommandConnection implements IConnect if (!jiraClient) { throw new NotLoggedInError(); } - const resource = (await jiraClient.getAccessibleResources()).find((r) => new URL(r.url).origin === this.instanceOrigin); - if (!resource) { - throw new CommandError("No-resource", "You do not have permission to create issues for this JIRA org"); + if (!this.projectUrl) { + throw new CommandError("No-resource-origin", "Room is configured with an ID and not a URL, cannot determine correct JIRA client"); } - return jiraClient.getClientForResource(resource); + const jiraProjectClient = await jiraClient.getClientForUrl(this.projectUrl); + if (!jiraProjectClient) { + throw new CommandError("No-resource", "You do not have permission to manage issues for this JIRA org"); + } + return jiraProjectClient; } @botCommand("create", "Create an issue for this project", ["type", "title"], ["description", "labels"], true) @@ -183,7 +186,7 @@ export class JiraProjectConnection extends CommandConnection implements IConnect throw new CommandError("invalid-issuetype", `You must specify a valid issue type (one of ${content}). E.g. ${this.commandPrefix} create ${project.issueTypes[0].name}`); } log.info(`Creating new issue on behalf of ${userId}`); - let result: any; + let result: JiraApi.JsonResponse; try { result = await api.addNewIssue({ //update: {}, diff --git a/src/Jira/Client.ts b/src/Jira/Client.ts index 9c078ead..75c4e87d 100644 --- a/src/Jira/Client.ts +++ b/src/Jira/Client.ts @@ -4,7 +4,9 @@ import JiraApi, { SearchUserOptions } from 'jira-client'; import QuickLRU from "@alloc/quick-lru"; import { JiraAccount, JiraAPIAccessibleResource, JiraIssue, JiraOAuthResult, JiraProject } from './Types'; import { BridgeConfigJira } from '../Config/Config'; +import LogWrapper from '../LogWrapper'; +const log = new LogWrapper("JiraClient"); const ACCESSIBLE_RESOURCE_CACHE_LIMIT = 100; const ACCESSIBLE_RESOURCE_CACHE_TTL_MS = 60000; @@ -13,11 +15,11 @@ export class HookshotJiraApi extends JiraApi { super(options); } - async getProject(projectIdOrKey: string) { + async getProject(projectIdOrKey: string): Promise { return await super.getProject(projectIdOrKey) as JiraProject; } - async getIssue(issueIdOrKey: string) { + async getIssue(issueIdOrKey: string): Promise { const res = await axios.get(`https://api.atlassian.com/${this.options.base}/rest/api/3/issue/${issueIdOrKey}`, { headers: { Authorization: `Bearer ${this.options.bearer}` @@ -60,6 +62,7 @@ export class JiraClient { // Existing failed promise, break out and try again. JiraClient.resourceCache.delete(this.bearer); } + await this.checkTokenAge(); const promise = (async () => { const res = await axios.get(`https://api.atlassian.com/oauth/token/accessible-resources`, { headers: { @@ -74,26 +77,28 @@ export class JiraClient { } async checkTokenAge() { + console.log("checkTokenAge:", this.oauth2State); if (this.oauth2State.expires_in + 60000 > Date.now()) { return; } + log.info(`Refreshing oauth token`); // Refresh the token - const res = await axios.post(`https://api.atlassian.com/oauth/token`, { + const res = await axios.post(`https://api.atlassian.com/oauth/token`, { grant_type: "refresh_token", client_id: this.config.oauth.client_id, client_secret: this.config.oauth.client_secret, refresh_token: this.oauth2State.refresh_token, }); - res.expires_in += Date.now() + (res.expires_in * 1000); - this.oauth2State = res; + const data = res.data as JiraOAuthResult; + data.expires_in += Date.now() + (data.expires_in * 1000); + this.oauth2State = data; + this.onTokenRefreshed(this.oauth2State); } - async getClientForName(name: string) { - const resources = await this.getAccessibleResources(); - const resource = resources.find((res) => res.name === name); - await this.checkTokenAge(); + async getClientForUrl(url: URL) { + const resource = (await this.getAccessibleResources()).find((r) => new URL(r.url).origin === url.origin); if (!resource) { - throw Error('User does not have access to this resource'); + return null; } return this.getClientForResource(resource); } diff --git a/src/Jira/Types.ts b/src/Jira/Types.ts index cad3a24f..3b7cb445 100644 --- a/src/Jira/Types.ts +++ b/src/Jira/Types.ts @@ -69,7 +69,7 @@ export interface JiraIssue { } export interface JiraOAuthResult { - state: string; + state?: string; access_token: string; refresh_token: string; expires_in: number; diff --git a/src/UserTokenStore.ts b/src/UserTokenStore.ts index 12a399a3..f4024671 100644 --- a/src/UserTokenStore.ts +++ b/src/UserTokenStore.ts @@ -3,7 +3,6 @@ import { GitLabClient } from "./Gitlab/Client"; import { Intent } from "matrix-bot-sdk"; import { promises as fs } from "fs"; import { publicEncrypt, privateDecrypt } from "crypto"; -import JiraApi from 'jira-client'; import LogWrapper from "./LogWrapper"; import { JiraClient } from "./Jira/Client"; import { JiraOAuthResult } from "./Jira/Types";