RelationsService.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import { Inject, Service } from '@tsed/di';
  2. import axios from 'axios';
  3. import { addHours } from 'date-fns';
  4. import { REQUEST_TIMEOUT_FOR_PTOG } from '@growi/slack';
  5. import { Relation } from '~/entities/relation';
  6. import { RelationRepository } from '~/repositories/relation';
  7. import loggerFactory from '~/utils/logger';
  8. const logger = loggerFactory('slackbot-proxy:services:RelationsService');
  9. type checkPermissionForInteractionsResults = {
  10. allowedRelations:Relation[],
  11. disallowedGrowiUrls:Set<string>,
  12. commandName:string,
  13. }
  14. @Service()
  15. export class RelationsService {
  16. @Inject()
  17. relationRepository: RelationRepository;
  18. async getSupportedGrowiCommands(relation:Relation):Promise<any> {
  19. // generate API URL
  20. const url = new URL('/_api/v3/slack-integration/supported-commands', relation.growiUri);
  21. return axios.get(url.toString(), {
  22. headers: {
  23. 'x-growi-ptog-tokens': relation.tokenPtoG,
  24. },
  25. timeout: REQUEST_TIMEOUT_FOR_PTOG,
  26. });
  27. }
  28. async syncSupportedGrowiCommands(relation:Relation): Promise<Relation> {
  29. const res = await this.getSupportedGrowiCommands(relation);
  30. const { permissionsForBroadcastUseCommands, permissionsForSingleUseCommands } = res.data.data;
  31. if (relation !== null) {
  32. relation.permissionsForBroadcastUseCommands = permissionsForBroadcastUseCommands;
  33. relation.permissionsForSingleUseCommands = permissionsForSingleUseCommands;
  34. relation.expiredAtCommands = addHours(new Date(), 48);
  35. return this.relationRepository.save(relation);
  36. }
  37. throw Error('No relation exists.');
  38. }
  39. async syncRelation(relation:Relation, baseDate:Date):Promise<Relation|null> {
  40. if (relation == null) return null;
  41. const distanceMillisecondsToExpiredAt = relation.getDistanceInMillisecondsToExpiredAt(baseDate);
  42. if (distanceMillisecondsToExpiredAt < 0) {
  43. try {
  44. return await this.syncSupportedGrowiCommands(relation);
  45. }
  46. catch (err) {
  47. logger.error(err);
  48. return null;
  49. }
  50. }
  51. // 24 hours
  52. if (distanceMillisecondsToExpiredAt < 24 * 60 * 60 * 1000) {
  53. try {
  54. this.syncSupportedGrowiCommands(relation);
  55. }
  56. catch (err) {
  57. logger.error(err);
  58. }
  59. }
  60. return relation;
  61. }
  62. async isPermissionsForSingleUseCommands(relation:Relation, growiCommandType:string, channelName:string, baseDate:Date):Promise<boolean> {
  63. const syncedRelation = await this.syncRelation(relation, baseDate);
  64. if (syncedRelation == null) {
  65. return false;
  66. }
  67. const permission = relation.permissionsForSingleUseCommands[growiCommandType];
  68. if (permission == null) {
  69. return false;
  70. }
  71. if (Array.isArray(permission)) {
  72. return permission.includes(channelName);
  73. }
  74. return permission;
  75. }
  76. async isPermissionsUseBroadcastCommands(relation:Relation, growiCommandType:string, channelName:string, baseDate:Date):Promise<boolean> {
  77. const syncedRelation = await this.syncRelation(relation, baseDate);
  78. if (syncedRelation == null) {
  79. return false;
  80. }
  81. const permission = relation.permissionsForBroadcastUseCommands[growiCommandType];
  82. if (permission == null) {
  83. return false;
  84. }
  85. if (Array.isArray(permission)) {
  86. return permission.includes(channelName);
  87. }
  88. return permission;
  89. }
  90. async checkPermissionForInteractions(
  91. relations:Relation[], actionId:string, callbackId:string, channelName:string,
  92. ):Promise<checkPermissionForInteractionsResults> {
  93. return this.checkEachRelation(relations, actionId, callbackId, channelName);
  94. }
  95. async checkEachRelation(
  96. relations:Relation[], actionId:string, callbackId:string, channelName:string,
  97. ):Promise<checkPermissionForInteractionsResults> {
  98. const allowedRelations:Relation[] = [];
  99. const disallowedGrowiUrls:Set<string> = new Set();
  100. let commandName = '';
  101. await relations.map(async(relation) => {
  102. let permissionForInteractions:boolean|string[];
  103. const singleUse = Object.keys(relation.permissionsForSingleUseCommands);
  104. const broadCastUse = Object.keys(relation.permissionsForBroadcastUseCommands);
  105. [...singleUse, ...broadCastUse].forEach(async(tempCommandName) => {
  106. // ex. search OR search:handlerName
  107. const commandRegExp = new RegExp(`(^${tempCommandName}$)|(^${tempCommandName}:\\w+)`);
  108. // skip this forEach loop if the requested command is not in permissionsForBroadcastUseCommands and permissionsForSingleUseCommands
  109. if (!commandRegExp.test(actionId) && !commandRegExp.test(callbackId)) {
  110. return;
  111. }
  112. commandName = tempCommandName;
  113. // case: singleUse
  114. permissionForInteractions = relation.permissionsForSingleUseCommands[tempCommandName];
  115. // case: broadcastUse
  116. if (permissionForInteractions == null) {
  117. permissionForInteractions = relation.permissionsForBroadcastUseCommands[tempCommandName];
  118. }
  119. if (permissionForInteractions === true) {
  120. return allowedRelations.push(relation);
  121. }
  122. // check permission at channel level
  123. if (Array.isArray(permissionForInteractions) && permissionForInteractions.includes(channelName)) {
  124. return allowedRelations.push(relation);
  125. }
  126. disallowedGrowiUrls.add(relation.growiUri);
  127. });
  128. });
  129. return { allowedRelations, disallowedGrowiUrls, commandName };
  130. }
  131. }