Skip to content

Commit

Permalink
feat: Add authorized-users
Browse files Browse the repository at this point in the history
  • Loading branch information
Takashicc committed Mar 21, 2024
1 parent aff738a commit 298fa30
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 34 deletions.
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ jobs:
channel-id: ${{ secrets.SLACK_CHANNEL_ID }}
mention-to-user: ${{ secrets.SLACK_MENTION_TO_USER }}
mention-to-group: ${{ secrets.SLACK_MENTION_TO_GROUP }}
authorized-users: ${{ secrets.SLACK_AUTHORIZED_USERS }}
timeout-minutes: 5
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jobs:
channel-id: ${{ secrets.SLACK_CHANNEL_ID }}
mention-to-user: ${{ secrets.SLACK_MENTION_TO_USER }}
mention-to-group: ${{ secrets.SLACK_MENTION_TO_GROUP }}
authorized-users: ${{ secrets.SLACK_AUTHORIZED_USERS }}
timeout-minutes: 10
```
Expand All @@ -44,6 +45,10 @@ jobs:
- Optional. Slack user ID to mention.
- `mention-to-group`
- Optional. Slack group ID to mention.
- `authorized-users`
- Optional. Slack user IDs who are authorized to approve or reject. Comma separated.
- e.g.,
- `authorized-users: xxxxxx,yyyyyy`

- Set `timeout-minutes`
- Set the time to wait for approval.
3 changes: 3 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ inputs:
mention-to-group:
description: "Slack group ID to mention"
required: false
authorized-users:
description: "Slack user IDs who are authorized to approve or reject"
required: false

branding:
icon: plus
Expand Down
97 changes: 80 additions & 17 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24933,7 +24933,7 @@ RedirectableRequest.prototype._processResponse = function (response) {
redirectUrl.protocol !== "https:" ||
redirectUrl.host !== currentHost &&
!isSubdomain(redirectUrl.host, currentHost)) {
removeMatchingHeaders(/^(?:authorization|cookie)$/i, this._options.headers);
removeMatchingHeaders(/^(?:(?:proxy-)?authorization|cookie)$/i, this._options.headers);
}

// Evaluate the beforeRedirect callback
Expand Down Expand Up @@ -76792,6 +76792,26 @@ try {
} catch (er) {}


/***/ }),

/***/ 69042:
/***/ ((__unused_webpack_module, exports) => {

"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.Inputs = void 0;
exports.Inputs = {
BotToken: "bot-token",
SigningSecret: "signing-secret",
AppToken: "app-token",
ChannelId: "channel-id",
MentionToUser: "mention-to-user",
MentionToGroup: "mention-to-group",
AuthorizedUsers: "authorized-users",
};


/***/ }),

/***/ 70885:
Expand Down Expand Up @@ -76856,20 +76876,23 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.getInputs = void 0;
const core = __importStar(__nccwpck_require__(42186));
const Option_1 = __nccwpck_require__(2569);
const constants_1 = __nccwpck_require__(69042);
function getInputs() {
const botToken = getRequiredInput("bot-token");
const signingSecret = getRequiredInput("signing-secret");
const appToken = getRequiredInput("app-token");
const channelId = getRequiredInput("channel-id");
const mentionToUser = getOptionalInput("mention-to-user");
const mentionToGroup = getOptionalInput("mention-to-group");
const botToken = getRequiredInput(constants_1.Inputs.BotToken);
const signingSecret = getRequiredInput(constants_1.Inputs.SigningSecret);
const appToken = getRequiredInput(constants_1.Inputs.AppToken);
const channelId = getRequiredInput(constants_1.Inputs.ChannelId);
const mentionToUser = getOptionalInput(constants_1.Inputs.MentionToUser);
const mentionToGroup = getOptionalInput(constants_1.Inputs.MentionToGroup);
const authorizedUsers = getOptionalListInput(constants_1.Inputs.AuthorizedUsers);
return {
botToken,
signingSecret,
appToken,
channelId,
mentionToUser,
mentionToGroup,
authorizedUsers,
};
}
exports.getInputs = getInputs;
Expand All @@ -76883,6 +76906,18 @@ function getOptionalInput(name) {
}
return (0, Option_1.some)(value);
}
function getOptionalListInput(name) {
const value = core.getInput(name);
if (value === "") {
return Option_1.none;
}
const res = [];
const values = value.split(",");
for (const v of values) {
res.push(v.trim());
}
return (0, Option_1.some)(res);
}


/***/ }),
Expand Down Expand Up @@ -77015,21 +77050,32 @@ function run(inputs, app) {
});
}))();
app.action("slack-approval-approve", ({ ack, client, body, logger }) => __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c;
var _a, _b;
yield ack();
const blockAction = body;
const userId = blockAction.user.id;
const ts = ((_a = blockAction.message) === null || _a === void 0 ? void 0 : _a.ts) || "";
if (!isAuthorizedUser(userId, inputs.authorizedUsers)) {
yield client.chat.postMessage({
channel: inputs.channelId,
thread_ts: ts,
text: `You are not authorized to approve this action: <@${userId}>`,
});
return;
}
try {
const response_blocks = (_a = body.message) === null || _a === void 0 ? void 0 : _a.blocks;
const response_blocks = (_b = blockAction.message) === null || _b === void 0 ? void 0 : _b.blocks;
response_blocks.pop();
response_blocks.push({
type: "section",
text: {
type: "mrkdwn",
text: `Approved by <@${body.user.id}> `,
text: `Approved by <@${userId}> `,
},
});
yield client.chat.update({
channel: ((_b = body.channel) === null || _b === void 0 ? void 0 : _b.id) || "",
ts: ((_c = body.message) === null || _c === void 0 ? void 0 : _c.ts) || "",
channel: inputs.channelId,
ts: ts,
blocks: response_blocks,
});
}
Expand All @@ -77039,21 +77085,32 @@ function run(inputs, app) {
process.exit(0);
}));
app.action("slack-approval-reject", ({ ack, client, body, logger }) => __awaiter(this, void 0, void 0, function* () {
var _d, _e, _f;
var _c, _d;
yield ack();
const blockAction = body;
const userId = blockAction.user.id;
const ts = ((_c = blockAction.message) === null || _c === void 0 ? void 0 : _c.ts) || "";
if (!isAuthorizedUser(userId, inputs.authorizedUsers)) {
yield client.chat.postMessage({
channel: inputs.channelId,
thread_ts: ts,
text: `You are not authorized to reject this action: <@${userId}>`,
});
return;
}
try {
const response_blocks = (_d = body.message) === null || _d === void 0 ? void 0 : _d.blocks;
const response_blocks = (_d = blockAction.message) === null || _d === void 0 ? void 0 : _d.blocks;
response_blocks.pop();
response_blocks.push({
type: "section",
text: {
type: "mrkdwn",
text: `Rejected by <@${body.user.id}>`,
text: `Rejected by <@${userId}>`,
},
});
yield client.chat.update({
channel: ((_e = body.channel) === null || _e === void 0 ? void 0 : _e.id) || "",
ts: ((_f = body.message) === null || _f === void 0 ? void 0 : _f.ts) || "",
channel: inputs.channelId,
ts: ts,
blocks: response_blocks,
});
}
Expand All @@ -77073,6 +77130,12 @@ function run(inputs, app) {
}
});
}
function isAuthorizedUser(userId, authorizedUsers) {
if ((0, Option_1.isNone)(authorizedUsers)) {
return true;
}
return authorizedUsers.value.includes(userId);
}
function main() {
return __awaiter(this, void 0, void 0, function* () {
const inputs = (0, input_helper_1.getInputs)();
Expand Down
11 changes: 11 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const Inputs = {
BotToken: "bot-token",
SigningSecret: "signing-secret",
AppToken: "app-token",
ChannelId: "channel-id",
MentionToUser: "mention-to-user",
MentionToGroup: "mention-to-group",
AuthorizedUsers: "authorized-users",
} as const;

export type Inputs = (typeof Inputs)[keyof typeof Inputs];
33 changes: 25 additions & 8 deletions src/helper/input_helper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as core from "@actions/core";
import { Option, none, some } from "fp-ts/lib/Option";
import { Inputs } from "../constants";

export type SlackApprovalInputs = {
botToken: string;
Expand All @@ -8,15 +9,17 @@ export type SlackApprovalInputs = {
channelId: string;
mentionToUser: Option<string>;
mentionToGroup: Option<string>;
authorizedUsers: Option<string[]>;
};

export function getInputs(): SlackApprovalInputs {
const botToken = getRequiredInput("bot-token");
const signingSecret = getRequiredInput("signing-secret");
const appToken = getRequiredInput("app-token");
const channelId = getRequiredInput("channel-id");
const mentionToUser = getOptionalInput("mention-to-user");
const mentionToGroup = getOptionalInput("mention-to-group");
const botToken = getRequiredInput(Inputs.BotToken);
const signingSecret = getRequiredInput(Inputs.SigningSecret);
const appToken = getRequiredInput(Inputs.AppToken);
const channelId = getRequiredInput(Inputs.ChannelId);
const mentionToUser = getOptionalInput(Inputs.MentionToUser);
const mentionToGroup = getOptionalInput(Inputs.MentionToGroup);
const authorizedUsers = getOptionalListInput(Inputs.AuthorizedUsers);

return {
botToken,
Expand All @@ -25,18 +28,32 @@ export function getInputs(): SlackApprovalInputs {
channelId,
mentionToUser,
mentionToGroup,
authorizedUsers,
};
}

function getRequiredInput(name: string): string {
function getRequiredInput(name: Inputs): string {
return core.getInput(name, { required: true });
}

function getOptionalInput(name: string): Option<string> {
function getOptionalInput(name: Inputs): Option<string> {
const value = core.getInput(name);
if (value === "") {
return none;
}

return some(value);
}

function getOptionalListInput(name: Inputs): Option<string[]> {
const value = core.getInput(name);
if (value === "") {
return none;
}
const res: string[] = [];
const values = value.split(",");
for (const v of values) {
res.push(v.trim());
}
return some(res);
}
57 changes: 48 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as core from "@actions/core";
import { App, BlockAction, LogLevel } from "@slack/bolt";
import { WebClient } from "@slack/web-api";
import { isSome } from "fp-ts/lib/Option";
import { Option, isNone, isSome } from "fp-ts/lib/Option";
import { getGitHubInfo } from "./helper/github_info_helper";
import { SlackApprovalInputs, getInputs } from "./helper/input_helper";

Expand Down Expand Up @@ -95,20 +95,34 @@ async function run(inputs: SlackApprovalInputs, app: App): Promise<void> {
"slack-approval-approve",
async ({ ack, client, body, logger }) => {
await ack();

const blockAction = <BlockAction>body;
const userId = blockAction.user.id;
const ts = blockAction.message?.ts || "";

if (!isAuthorizedUser(userId, inputs.authorizedUsers)) {
await client.chat.postMessage({
channel: inputs.channelId,
thread_ts: ts,
text: `You are not authorized to approve this action: <@${userId}>`,
});
return;
}

try {
const response_blocks = (<BlockAction>body).message?.blocks;
const response_blocks = blockAction.message?.blocks;
response_blocks.pop();
response_blocks.push({
type: "section",
text: {
type: "mrkdwn",
text: `Approved by <@${body.user.id}> `,
text: `Approved by <@${userId}> `,
},
});

await client.chat.update({
channel: body.channel?.id || "",
ts: (<BlockAction>body).message?.ts || "",
channel: inputs.channelId,
ts: ts,
blocks: response_blocks,
});
} catch (error) {
Expand All @@ -123,20 +137,34 @@ async function run(inputs: SlackApprovalInputs, app: App): Promise<void> {
"slack-approval-reject",
async ({ ack, client, body, logger }) => {
await ack();

const blockAction = <BlockAction>body;
const userId = blockAction.user.id;
const ts = blockAction.message?.ts || "";

if (!isAuthorizedUser(userId, inputs.authorizedUsers)) {
await client.chat.postMessage({
channel: inputs.channelId,
thread_ts: ts,
text: `You are not authorized to reject this action: <@${userId}>`,
});
return;
}

try {
const response_blocks = (<BlockAction>body).message?.blocks;
const response_blocks = blockAction.message?.blocks;
response_blocks.pop();
response_blocks.push({
type: "section",
text: {
type: "mrkdwn",
text: `Rejected by <@${body.user.id}>`,
text: `Rejected by <@${userId}>`,
},
});

await client.chat.update({
channel: body.channel?.id || "",
ts: (<BlockAction>body).message?.ts || "",
channel: inputs.channelId,
ts: ts,
blocks: response_blocks,
});
} catch (error) {
Expand All @@ -156,6 +184,17 @@ async function run(inputs: SlackApprovalInputs, app: App): Promise<void> {
}
}

function isAuthorizedUser(
userId: string,
authorizedUsers: Option<string[]>,
): boolean {
if (isNone(authorizedUsers)) {
return true;
}

return authorizedUsers.value.includes(userId);
}

async function main() {
const inputs = getInputs();

Expand Down

0 comments on commit 298fa30

Please sign in to comment.