디스코urs(Discourse) 사이트에 JS 플러그인을 하나 가지고 있습니다. 이 JS는 데이터를 저장하기 위해 Pocketbase를 사용합니다. Pocketbase는 파일과 JSON 블롭을 저장할 수 있는 “서버리스” SQLite 서비스입니다. Pocketbase에는 뛰어난 JWT 기반 인증 시스템이 있어, 사용자가 인증 토큰을 한 번만 확보하면 백엔드 서버를 통해 프록싱하는 등의 과정 없이 클라이언트 측 JS에서 직접 Pocketbase에 데이터를 안전하게 저장할 수 있습니다.
현재 저는 사용자가 디스코urs에 로그인할 때 Pocketbase 측에서 자동으로 로그인을 생성하는 방법을 찾고 있습니다.
첫 번째 시도는 JS 플러그인이 서버의 특정 경로에 요청을 보내면서 디스코urs 인증 쿠키를 포함하는 것이었습니다. 그런 다음 해당 경로에 대해 nginx가 서비스를(“로그인 프록시”)로 프록시하도록 하여, 인증 쿠키를 디코딩하고 사용자를 식별하게 하려 했습니다. 검증된 사용자 정보가 있으면, 로그인 프록시는 Pocketbase에 특별한 호출을 하여 Pocketbase 인증 쿠키를 받아오고, 이를 클라이언트 측 JS가 Pocketbase로 직접 후속 요청을 보낼 수 있도록 반환하는 방식이었습니다.
하지만 디스코urs 인증 쿠키를 디코딩하는 데 어려움을 겪고 있습니다. (_t가 올바른 쿠키라고 생각하지만, 사용자 세부 정보에 접근하는 간단한 방법을 찾지 못했고, 구조가 언제든 변경될 수 있다는 점도 우려됩니다.)
로그인한 사용자의 이메일 주소를 안전하게 가져오는 더 현명한 방법이 있을까요? 이는 클라이언트 측에서 이루어져서는 안 된다고 생각하며, 명백한 보안상의 이유로 서버 측에서 처리하는 것이 좋습니다.
제 경우, 단일 도메인으로 모든 것을 설정하고 fetch 호출이 리다이렉트를 투명하게 따르기 때문에, { credentials: "include" } 옵션을 가진 단일 클라이언트 측 fetch 호출로 이를 처리하고 있습니다.
클라이언트는 커스텀 /auth/token을 요청합니다. 이 엔드포인트는 _t의 존재를 확인(그렇지 않을 경우 불필요한 리다이렉트를 방지하기 위함)하고, 링크된 토픽의 문서에 따라 nonce/sso/sig를 포함하여 구성한 보안이 적용된 /session/sso_provider URL로 리다이렉트를 반환합니다. 이때 return_sso_url은 커스텀 /auth/callback을 가리키며, 이 콜백에서는 Discourse가 전송한 데이터를 추출하여 클라이언트가 이 시점부터 사용할 수 있는 JWT 토큰을 생성하고 반환합니다.
하지만 제 생각에는 이 방법은 사용자를 보조 사이트로 보내고 그곳에서 인증을 관리하고 싶을 때 사용하는 것 같습니다.
제 경우에는 Discourse 사이트에서 실행되는 JS 코드가 있습니다. 그리고 이 JS가 같은 서버의 특정 경로로 호출되어 pocketbase용 쿠키를 받아오기를 원합니다.
실제로 저는 Discourse 앞에 nginx 프록시를 사용하고 있으며, 그래서 특수한 경로 /pb/auth (예시)를 추가했습니다. 제 JS가 그 경로에 도달하면, Discourse 내부에 있지 않은 백엔드 프록시 서버가 해당 연결을 받아 _t 세션 쿠키를 디코딩하려고 합니다.
이렇게 한 이유는 Discourse 플러그인을 추가하는 것보다 조금 더 쉬운 것 같아서였습니다 (플러그인 개발 및 개발 환경 설정에 익숙하지 않아서요). base64와 sha 해시를 사용하여 쿠키를 디코딩하는 것만으로 사용자를 식별하는 안전한 페이로드를 얻을 수 있다면 그렇게 하기로 했습니다.
하지만 Discourse에 이 경로를 추가하는 플러그인을 만드는 것이 단순한 일이라고 생각하신다면, 그 방법을 시도해 보고 싶습니다. 장기적으로는 올바른 방법인 것 같습니다. 하지만 저는 오래된 Perl 프로그래머라서 게으른 방법을 선호하는데, 제 nginx 경로 설정이 더 게으른 방법인 것 같았습니다.
한눈에 봐도 질문이 하나 있습니다. 이 튜토리얼은 discourse의 기본 rails 설치에 코드를 추가하는 건가요? 그것이 공식적인 방식이라면 그렇게 하는 데에는 문제가 없지만, 위험해 보이고 플러그인으로 처리하는 것이 더 나은 것 같습니다(플러그인은 쉽게 제거하거나 비활성화할 수 있으니까요). 또한, 제 코드가 github 저장소에 없다면 discourse 업그레이드 시 문제가 생기는 것에 대해 걱정해야 하는 것 아닌가요?
예를 들어 여기:
이것은 제가 실제로 컨테이너(./launcher enter app)에 들어가서 /var/www/app/controllers/snack_controller.rb를 편집해야 한다는 뜻인가요?
그리고, 실제로 해당 지시를 따랐습니다. ./launcher rebuild app를 실행한 후에도 /admin/snack.json 라우트가 작동하지 않습니다.
음, 설명서를 따라 해보려 했어요. 이 명령어 rake plugin:create[pocketbase-auth]를 컨테이너 내부에서 실행해 봤습니다(컨테이너 외부에서는 rake를 사용할 수 없으니까요, 맞죠?). 하지만 컨테이너 내부에 git이 설정되어 있지 않아서 실행이 실패했습니다.
더 읽어보니, 관리자 섹션에 플러그인을 표시하려면 플러그인의 git 저장소를 지정해야 하는 것 같습니다. 하지만 아직 플러그인의 제대로 작동하는 버전조차 만들지 못한 상태이고, git 저장소도 없습니다.
수정: 자세히 읽지 못했네요. 실제로는 개발 환경(dev setup)이 필요하며, 이는 문서 초반에 분명히 명시되어 있습니다. 이 부분을 작업한 뒤 다시 돌아올게요.
보통 플러그인은 discourse의 “dev” 설정에 맞춰 개발하는 것이지, 제가 실행 중인 docker 컨테이너 내부에서 개발하는 것이 아닐 것 같다는 생각이 듭니다. 그렇게 하는 것은 괜찮지만, 플러그인 개발 가이드가 그 전제와 해당 방식으로 실행하는 방법에 대한 설명으로 시작했으면 좋겠습니다. discourse를 실행하는 권장 방식은 docker를 사용하는 것인데(저는 docker를 좋아합니다), docker 내부에서 실행하는 방법과 문서에서 안내하는 개발 방식 사이에 차이가 있는 것 같습니다.
# rake plugin:create[pocketbase-auth]
Cloning 'https://github.com/discourse/discourse-plugin-skeleton' to '/var/www/discourse/plugins/pocketbase-auth'...
Initializing git repository...
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint:
hint: git config --global init.defaultBranch <name>
hint:
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint:
hint: git branch -m <name>
Initialized empty Git repository in /var/www/discourse/plugins/pocketbase-auth/.git/
Author identity unknown
*** Please tell me who you are.
Run
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
to set your account's default identity.
Omit --global to set the identity only in this repository.
fatal: unable to auto-detect email address (got 'discourse@community-public-do-vm-app.(none)')
rake aborted!
Command failed with exit 128: git
/var/www/discourse/lib/tasks/plugin.rake:356:in `system'
/var/www/discourse/lib/tasks/plugin.rake:356:in `block (2 levels) in <main>'
/var/www/discourse/lib/tasks/plugin.rake:346:in `chdir'
/var/www/discourse/lib/tasks/plugin.rake:346:in `block in <main>'
/usr/local/bin/bundle:25:in `load'
/usr/local/bin/bundle:25:in `<main>'
Tasks: TOP => plugin:create
(See full trace by running task with --trace)