Slack App 만들기

[Slack App] 슬랙 앱 만들기 - 배포 준비하기(oauth처리 + 워크스페이스에 앱 설치)

dohye1 2023. 1. 28. 13:34
반응형

이제 앱 배포를 준비해보자!

다른 워크스페이스에서 내가 만든 앱을 설치하려면 oauth과정을 처리해야한다.

 

사실 난 이 단계에서 엄청 막혔었다

공식문서를 읽어도 flow는 나오는데, 실제로 구현을 할땐 많이 막혔었당...ㅜㅠ

 

공식문서에서는 아래와 같은 flow로 구현을 하면된다고한다!

구현하기전에 공식문서에서 정리한 내용을 읽어보고하면 좋다

authentication flow

필요한것
1. @slack/bolt기반으로 작성한 코드
2. https로 시작하는 redirect url > 만약 local에서 테스트를 해보는거라면 ngrok으로 하면된다. 아래에 설명이 있음
3. app dashboard에 Redirect url 적어주기

local에서 테스트해보는경우

만약 local에서 테스트하는데 https 주소가 필요한경우가 있다면 

ngrok을 사용하면된다!!!

설치방법은 여기에서 확인해보세요

 

하나의 터미널을 켜서 나의 앱을 실행시킨다음, 다른 터미널을 켜서

$ ngrok http PORT_NUMBER

PORT_NUMBER부분에 로컬에서 지금 앱이 작동되고있는 포트번호를 적어주면 이런 화면이 실행된다.

그럼 저기 forwarding과 같은줄에있는 https://~~.jp.ngrok.io라는 주소를 사용하면된다

localhost:PORT에서의 실행을 종료하면 이 주소도 포워딩할대상이 없기때문에, 주의하기!!!!

 

그리고 저 주소를 복사해서 아래의 Redirect URLs에 붙여넣어준다.

여기서 주의할점은 https로 시작하는 주소 + path설정해주기

나는   /install  이라는 경로로 받도록했다.

그리고 저 경로를 서버코드에서 받도록 처리해야한다.

이름은 마음대로 지으면됨!!

 

그리고 manage distribution 페이지에 들어가서 아래쪽으로 내려가보면 

Share Your App with Other Workspaces라는 영역이 있는데 아래와 같은 항목들을 확인해야한다.

1. 사용하고자 하는 기능에 대한 확인: event, command, interactive component(button같은거)를 설정했는지 그냥 확인만하면됨

2. oauth redirect url: 이건 바로 위에서 작업함

3. token이나 url같은 내용이 코드에 적혀있는지 확인하는 부분인데, 그냥 체크해도 넘어가지긴했음

4. 앱 설치 redirect url이 https로 시작해야함

 

암튼 체크가 안된 항목들만 더 확인한다음 public distribution버튼을 누르면 된다!

그럼 아래처럼 뷰가 변경될것이다!

인증code 받기

앱 설치화면을 어떻게 여는지 알아보자!

 

app dashboard > manage distribution페이지에 들어가보면 아래의 화면이 보인다!

저 add to slack 버튼을 누르면 내가 사용하고자하는 권한들에 대한 설명과, 이 앱을 설치할 채널을 선택한 뒤 허용버튼을 누르면 

내가 지정한 Redirect URL로 리다이렉트된다.

 

그럼 이제 서버에서는 어떻게 저 요청을 받는지 보자

CustomRoute 설정

@slack/bolt에 customroute를 추가해줄수있다 

먼저 customRoutes를 만들어야한다.

 

아래의 코드를 보면 path를  /install 로 적어주었다.

app dashboard에 적어준 url의 path와 동일하게 적어줘야한다.

const customRoutes: CustomRoute[] = [
  {
    path: "/install",
    method: ["GET"],
    handler: async (req, res) => {
    
    // FIXME: 난 regex를 쓰지않았지만,,,.. 암튼 req.url에 있는 code값을 받으면된다!
	const code = await req
        .url!.replace("/install?code=", "")
        .replace("&state=", "");

      // 아래의 url로 요청을 보내야한다.
      // 아래 요청과 관련된 내용은 https://api.slack.com/methods/oauth.v2.access 여기서 확인가능
      const slackUrl = `https://slack.com/api/oauth.v2.access`;
      const details = {
        code,
        client_id: AppCredential.clientId,
        client_secret: AppCredential.clientSecret,
      };

      const formBodyStr = generateFormBodyStr(details);

      const _headers = {
        "Content-Type": "application/x-www-form-urlencoded",
      };

      const config = {
        method: "POST",
        url: slackUrl,
        data: formBodyStr,
        headers: _headers,
      };
      try {
        const result = await axios(config);
        // 결과값을 db에 저장해놔야한다.
        // 그래야 아래의 정보들을 사용해 특정한 워크스페이스에 접근이 가능하다.
        if (result) {
          const teamInfo = {
            enterpriseId: result.data.enterprise?.id,
            teamId: result.data.team.id,
            botToken: result.data.access_token,
            botId: result.data.bot_id,
            botUserId: result.data.bot_user_id,
          };
          // teamInfo를 db에 저장해준다.
          DB에_저장하기
        }
        
        // 그리고 다른 페이지로 반환해야하는데, 아래의 주소로 반환하게되면
        // 브라우저에 "슬랙으로 이동하기"창이 뜬다.
        res.setHeader(
          "Location",
          `slack://app${
            result
              ? `?id=${result.data.app_id}&team=${result.data.team.id}`
              : ""
          }`
        );
        res.statusCode = 302;
        res.end();
      } catch (error) {
        console.log(error);
      }
    },
  },
];


const generateFormBodyStr = (body: Record<string, string>) => {
  const formBody = [];
  for (let property in body) {
    const encodedKey = encodeURIComponent(property);
    // @ts-ignore
    const encodedValue = encodeURIComponent(body[property]);
    formBody.push(encodedKey + "=" + encodedValue);
  }
  return formBody.join("&");
};

그리고 나서 이 customRoute를 어디에 추가해줘야하나면

import { App } from "@slack/bolt";

export default new App({
  credential: ??,
  authorize: ??,
  port: PORT,
  customRoutes,
});

App을 생성하는곳에서 customRoutes에 설정해주면된다!

 

이렇게 하면 슬랙에 앱이 추가될거다!

 

그럼 앱에 접근할때, 저 authorize에 

const authorize: Authorize<boolean> = async ({ teamId, enterpriseId }) => {
  try {
    const workspace = teamId, enterpriseId를 사용해 내가 접근한 워크스페이스의 정보를 DB에서 받아온다
    
    if (workspace) {
      return Promise.resolve({
        botToken: workspace.botToken,
        botUserId: workspace.botUserId,
      });
    }
    return {};
  } catch (error) {
    throw new Error("계정이 없음");
  }
};

이 함수의 반환값을 넘겨주면된다!

 

그럼 이제 앱이 잘 켜질것이다!

 

추가로 작업해야할것들

1. 앱을 uninstall할때 db에 저장된 워크스페이스를 지워줘야한다!

앱이 uninstall 되면  app_uninstalled  이벤트가 발생한다.

이 이벤트에 대한 처리를 해주면된다!

 

2. 그리고 사용자가 앱을 설치한상태에서 또 설치를 하게되면 db에 동일한 워크스페이스에 대한 데이터가 여러개 쌓이게되니

이미 앱을 설치한 워크스페이스에서 또 설치를하는경우 새로운 데이터를 생성하지않도록 방어 코드를 추가해야한다


아직 해결하지못한 부분

1. enterprise는 모두 null인데, 언제 값이 채워져서 넘어오는거지..?

2. user token을 활용하는방법...ㅜㅠ