빌드

이건 누가 눌러도 되나 — 버튼을 숨기는 것은 권한이 아니다

작성: makemind · 2026년 7월 9일

매장 포스에 "매출 취소" 버튼이 있다. 점장만 눌러야 한다.

보통 이렇게 한다. 로그인한 사람이 알바면 그 버튼을 안 그린다.

그리고 그게 권한 처리라고 부른다. 아니다.

안 그리는 것으로는 아무것도 못 막는다

버튼을 안 그리는 게 막는 것은 그 버튼을 눌러서 요청이 나가는 경로 하나뿐이다. 요청은 다른 데서도 나갈 수 있다. 화면 정의를 고칠 수도 있고, 아예 화면 없이 도구를 부를 수도 있다. 화면은 클라이언트고, 클라이언트는 남의 것이다.

이 샘플은 그래서 반대로 만들었다. 버튼을 둘 다에게 보여 준다.

SIGNED IN AS STAFF — 4 sales · 9.6k won. Ring / Void last sale / Open drawer. voided 0 · refused 2
SIGNED IN AS STAFF — 4 sales · 9.6k won. Ring / Void last sale / Open drawer. voided 0 · refused 2

알바 화면이다. "Void last sale" 도 "Open drawer" 도 그대로 있다. 누를 수 있다.

누르면 이렇게 된다.

[staff] sale.ring   -> "rang 3000"                        (sales=4 voided=0 refused=0)
[staff] sale.void   -> "sale.void is not yours to press"  (sales=4 voided=0 refused=1)
[staff] drawer.open -> "drawer.open is not yours to press"(sales=4 voided=0 refused=2)

팔기는 했고, 취소는 안 됐다. 화면이 막은 게 아니라 서버가 안 한 것이다.

점장이 같은 화면을 연다

SIGNED IN AS MANAGER — 3 sales · 6.6k won. voided 1 · refused 0
SIGNED IN AS MANAGER — 3 sales · 6.6k won. voided 1 · refused 0
[manager] sale.ring   -> "rang 3000"     (sales=4 voided=0 refused=0)
[manager] sale.void   -> "voided 3000"   (sales=3 voided=1 refused=0)
[manager] drawer.open -> "drawer opened" (sales=3 voided=1 refused=0)

같은 버튼, 같은 순서, 다른 결과.

그리고 화면 파일은 같은 파일이다. 이게 이 편의 주장이라서 하니스가 해시를 찍는다.

screen ui/pages/register.json sha256:72288a2c655a — one file, both roles
the screen offers 3 buttons and shows all of them to everyone

역할은 인자가 아니다

여기가 이 샘플의 유일한 설계 결정이다.

/// Who this session is. Fixed for its lifetime, never taken from a call.
final String role;

sale.voidrole 을 인자로 받는다고 해 보자.

sale.void { "role": "manager" }

그러면 부를 수 있는 사람은 누구나 점장이라고 말할 수 있다. 그 순간 진짜로 막고 있는 건 화면이 그 값을 안 보내 준다는 것뿐이고, 우리는 다시 "버튼을 안 그린다"로 돌아온다.

그래서 역할은 세션이 열릴 때 정해진다. 이 샘플에서는 실행 인자로, 실제 배포라면 그 연결이 인증한 것으로.

final role = args
    .firstWhere((a) => a.startsWith('--role='), orElse: () => '--role=staff')
    .substring('--role='.length);

화면은 역할을 보내지 않는다. 보내고 싶어도 보낼 수가 없다.

검증이 그걸 지킨다 — 화면 정의에 역할 이름이 나타나면 실패한다.

# The screen may *display* the role it was told. It must never name one — the
# moment "manager" or "staff" appears in a screen definition, some branch is
# being decided in the wrong place.
if grep -qiE '"[^"]*(manager|staff)' register.mbd/ui/pages/register.json; then
  echo "   the screen definition names a role — the rule has leaked out of the tool"
  exit 1
fi

화면은 {{role}}표시한다. 그건 서버가 말해 준 것을 그리는 일이고, 아무것도 결정하지 않는다.

이 콘텐츠는 개발자 이상이 필요합니다

로그인 후 플랜을 업그레이드하면 계속 읽을 수 있습니다.

플랜 보기
Twitter