n8n Cloud Code 노드에서 외부 npm 모듈 없이 bcrypt 서명 만들기
n8n Cloud의 Code 노드는 require로 외부 npm 모듈을 불러올 수 없습니다. 네이버 커머스 API 인증에 필요한 bcrypt 해시를 순수 자바스크립트 코드로 노드 안에 직접 넣어서, require 없이 원래 라이브러리와 동일한 결과를 만들고 워크플로우를 다시 정상적으로 실행했습니다.
배경
네이버 커머스 API는 인증 토큰을 받기 전에 client_id와 타임스탬프를 bcrypt로 해싱한 전자서명을 요구합니다. 다른 해시 방식으로는 유효한 서명이 나오지 않기 때문에 bcrypt 사용이 사실상 강제됩니다.
n8n 워크플로우의 Code 노드에 const bcrypt = require('bcryptjs')를 넣고 실행했더니 매번 다음 에러가 났습니다.
Module 'bcryptjs' is disallowed [line 1]
n8n Cloud의 Code 노드는 보안을 위해 외부 npm 모듈 사용을 막아둡니다. 셀프 호스팅 버전에는 NODE_FUNCTION_ALLOW_EXTERNAL 환경변수로 특정 모듈을 허용하는 설정이 있지만, Cloud 사용자는 서버 환경변수에 접근할 수 없어 이 방법을 쓸 수 없습니다.
목표
require없이 Code 노드 안에서 bcrypt 해시를 계산합니다.- 결과 해시가 npm
bcryptjs패키지와 완전히 같아야 합니다. 네이버 API가 다른 해시 방식은 받아주지 않기 때문입니다. - 타임스탬프 관리, 조회 구간 계산 등 노드의 나머지 로직은 그대로 유지합니다.
구현 흐름
대체 해시로 우회할 수 없었던 이유
가장 먼저 든 생각은 "SHA-256처럼 Node 내장 crypto 모듈로 되는 해시로 바꾸면 되지 않을까"였습니다. 하지만 네이버 커머스 API 문서는 전자서명 생성 방식을 bcrypt 해싱으로 못박아두고 있고, 다른 알고리즘으로 만든 값은 인증 토큰 발급 단계에서 거부됩니다. 그래서 알고리즘을 바꾸는 대신 bcrypt 자체를 어떻게든 Code 노드 안으로 들고 오는 방향으로 정했습니다.
bcryptjs가 애초에 순수 자바스크립트였다는 점
bcrypt라는 이름의 패키지는 네이티브 바인딩이 필요해 별도 컴파일 과정을 거치지만, bcryptjs는 이름 그대로 순수 자바스크립트로만 작성되어 있습니다. 즉 require 없이 해당 라이브러리의 해싱 함수 코드를 그대로 노드 안에 붙여 넣어도 동일하게 동작한다는 뜻입니다. require('bcryptjs') 한 줄을, 같은 알고리즘을 계산하는 함수 선언 코드로 바꾸는 방식으로 문제를 풀었습니다.
라이브러리를 그대로 옮길 때 걸린 두 번째 에러
알고리즘 코드를 그대로 옮기고 나니 새로운 에러가 났습니다.
Cannot assign to read only property 'now' of function 'function Date() { [native code] }'
원본 라이브러리에는 오래된 자바스크립트 환경을 위한 방어 코드로 Date.now = Date.now || function() { ... } 같은 구문이 들어 있습니다. n8n의 Code 노드 실행 환경은 내장 객체 Date를 수정하지 못하게 막아두고 있어서, Date.now가 이미 존재하는데도 재할당을 시도하는 순간 에러가 났습니다. 다음과 같이 조건을 먼저 확인하도록 고쳐서 애초에 재할당이 실행되지 않게 만들었습니다.
if (typeof Date.now !== 'function') {
Date.now = function () {
return +new Date();
};
}
결과 검증 방법
노드 안에 붙여 넣기 전에, 같은 client_id와 타임스탬프 값으로 로컬 Node.js 환경에서 원본 bcryptjs 패키지와 인라인한 코드의 해시 결과를 나란히 계산해 비교했습니다. 두 결과가 문자 단위로 완전히 같다는 것을 확인한 뒤에야 실제 워크플로우에 반영했습니다. 이 순서를 지키지 않았다면 n8n 화면에서 에러 없이 실행되더라도 네이버 API가 서명을 거부하는 상황에서 원인을 다시 거슬러 올라가야 했을 것입니다.
결과
Code 노드가 에러 없이 실행되고 client_id, timestamp, signature, fromStr, toStr 다섯 개 필드가 정상적으로 출력됩니다. 로컬에서 미리 검증한 대로 원본 bcryptjs와 바이트 단위로 동일한 해시가 나왔고, 이 해시로 실제 인증 토큰 발급까지 성공했습니다.
배운 점
외부 모듈이 막혔다고 해서 다른 알고리즘으로 우회할 수 있는 것은 아니었습니다. 상대 API 스펙이 알고리즘을 특정 방식으로 못박아두면, 대체재를 찾기보다 그 알고리즘 자체를 코드로 들고 오는 편이 더 빠른 답이었습니다.
순수 자바스크립트로 작성된 라이브러리는 "외부 모듈 금지" 환경에서도 소스 코드만 그대로 옮기면 동작합니다. 문제는 require라는 호출 방식이었지, 알고리즘 자체가 막힌 것은 아니었습니다.
브라우저 코드 에디터에 1,000줄이 넘는 코드를 붙여 넣을 때도 생각지 못한 함정이 있었습니다. 에디터가 화면에 보이는 부분만 그려주는 가상화 방식이면, 전체 선택 단축키를 눌러도 실제로는 화면에 보이는 부분만 선택되는 경우가 있습니다. 커서를 문서 처음과 끝으로 각각 이동시키는 방식으로 선택 범위를 넓혀야 전체 내용이 안전하게 교체됩니다.
다음 액션
이번에 정리한 순수 JS bcrypt 코드는 비슷한 인증 방식을 쓰는 다른 워크플로우에도 그대로 재사용할 수 있습니다. Code 노드에서 막히기 쉬운 다른 외부 모듈도 순수 JS 대체가 가능한지 미리 점검해두면, 같은 에러를 다시 만났을 때 대응 시간을 크게 줄일 수 있을 것입니다.