Help us shape the future of Go! Take the 2026 Go Developer Survey

dilion

package module
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 19, 2026 License: Apache-2.0 Imports: 25 Imported by: 0

README

Dilion

한국어 · English

Dilion은 Go와 PostgreSQL로 만든 인증·개인정보 관리 서버입니다. Supabase Auth 호환 API로 회원가입과 로그인을 처리하고, 같은 사용자 ID를 기준으로 개인정보 보관, 동의 이력, 회원 탈퇴에 따른 삭제 작업을 관리합니다.

개인정보는 Vault에 암호화해 저장하고, 조회 권한에 따라 마스킹하거나 원문을 제공합니다. 동의와 철회는 이력으로 남기며, 삭제 요청은 유예 기간을 거쳐 비동기 작업으로 처리합니다. 외부 시스템의 삭제 처리는 커넥터로 연결할 수 있습니다. API 호출 예시와 처리 제약은 사용 사례에 정리되어 있습니다.

서버를 독립 프로세스로 실행하거나 dilion.NewServer(...)로 Go 애플리케이션에 넣을 수 있습니다. 메일·SMS, KMS, 훅, 삭제 커넥터는 Go 인터페이스로 교체할 수 있습니다.

로컬 실행

Go 1.26.8 이상, Docker Compose, GNU Make가 필요합니다.

make dev

PostgreSQL을 localhost:55432에 띄우고 마이그레이션을 적용한 뒤, API 서버를 http://localhost:8787에서 실행합니다.

curl http://localhost:8787/healthz

샘플 관리 화면은 다른 터미널에서 실행합니다. Node.js 22 이상이 필요합니다.

make web-install
make dev-web

화면 주소는 http://localhost:5173입니다. 관리 API용 개발 토큰 설정은 web/README.md를 참고하세요. service_role 토큰을 운영 브라우저에 노출하면 안 됩니다.

개발용 PII 암호화 키는 .dev/master.key에 보관됩니다. 이 파일을 잃으면 기존 Vault 데이터를 복호화할 수 없습니다. DB를 보존하며 중지하려면 docker compose stop을 사용하세요. make down은 DB 볼륨도 삭제합니다.

배포 이미지와 패키지

릴리스는 커밋 메시지로 시작합니다. js/packages/auth-js/package.json의 버전을 올리고 제목이 정확히 chore(release): v<버전>인 커밋을 기본 브랜치에 푸시하면, 워크플로가 v<버전> 태그를 만든 뒤 서버 이미지와 JS SDK를 같은 버전으로 함께 발행합니다.

# js/packages/auth-js/package.json 의 version 을 0.1.0-rc1 로 올린 뒤
git commit -am 'chore(release): v0.1.0-rc1'
git push

태그는 릴리스 봇(dilion-release[bot])이 만들며, 그 태그 푸시가 실제 발행 실행을 시작합니다. 따라서 릴리스 하나는 워크플로 실행 두 번으로 나뉩니다 — 커밋 푸시가 태그를 만들고, 태그 푸시가 게이트와 발행을 수행합니다. 발행은 항상 태그에서 실행되므로 npm provenance와 이미지 attestation에도 브랜치가 아닌 태그가 기록됩니다.

v* 태그를 직접 푸시해도 같은 릴리스가 실행되며, 이때는 태그 생성 실행만 없습니다. 커밋이 말하는 버전과 package.json 의 버전이 어긋나면 태그도 만들지 않고 아무것도 발행하지 않은 채 실패합니다. 제목이 위 형식과 정확히 일치하지 않는 커밋은 릴리스로 보지 않으며, 릴리스처럼 보이는 경우에만 경고를 남깁니다.

서버 이미지는 GitHub Container Registry에 linux/amd64와 linux/arm64로 올라갑니다.

docker run --rm -p 8787:8787 \
  -e DILION_DSN='postgres://dilion:dilion@host:5432/dilion' \
  ghcr.io/dilion-io/dilion:latest

마이그레이션은 서버가 시작할 때 직접 적용하므로 별도 단계가 필요하지 않습니다. DILION_JWT_SECRET과 DILION_MASTER_KEY를 주지 않으면 부팅마다 임시 키를 만들므로 운영에서는 반드시 지정해야 합니다. 재시작하면 발급한 토큰이 무효가 되고 저장된 개인정보를 복호화할 수 없습니다. 환경 변수 전체 목록은 cmd/dilion/main.go의 주석에 있습니다.

같은 이미지를 로컬에서 빌드하려면 make docker-build를 사용합니다.

JS SDK는 npm에 발행됩니다.

npm install @dilion-io/auth-js

사전 릴리스(v0.1.0-rc1 등)는 npm next 태그로만 올라가고 이미지의 latest도 갱신하지 않습니다.

인증과 SDK

인증 API의 기본 경로는 /auth/v1입니다. 이메일·비밀번호, OTP, OAuth/OIDC, Passkey, MFA, SAML SSO를 구현하며, 외부 제공자를 사용하는 방식은 별도 설정이 필요합니다. 개인정보 API는 /privacy/v1, 역할·권한·API 키 관리는 /iam/v1에서 제공합니다.

일반 인증에는 @supabase/supabase-js를 사용할 수 있습니다. OPAQUE를 사용하려면 저장소의 @dilion-io/auth-js를 사용하세요. 이 SDK는 Supabase 라이브러리를 의존성으로 가져오고, 기존 클라이언트에 auth.opaque를 추가합니다. 기존 auth.signUp()이나 auth.signInWithPassword()의 동작은 바꾸지 않습니다. 빌드 방법은 js/README.md에 있습니다.

OPAQUE 회원가입과 로그인

OPAQUE는 비밀번호를 서버에 보내지 않고 가입·로그인하는 인증 방식입니다. 서버에 DILION_AUTH_OPAQUE_MASTER_KEY를 설정하면 활성화됩니다. 값은 무작위 32바이트를 패딩 없는 base64url로 인코딩한 문자열이어야 합니다. 이 키는 PII 암호화 키와 별개이며, 재시작 후에도 유지하고 모든 서버 복제본에서 동일하게 사용해야 합니다. 명시적으로 끄려면 DILION_AUTH_OPAQUE_ENABLED=false를 설정하세요.

import { createClient } from '@dilion-io/auth-js'

const client = createClient('https://auth.example.com', 'your-anon-key')

const signup = await client.auth.opaque.signUp({
  email: 'user@example.com',
  password: 'user-supplied-password',
})
if (signup.error) throw signup.error

신규 가입에는 기존 로그인 세션이 필요하지 않으며, 서버에 일반 비밀번호 해시를 만들지 않습니다. 가입 응답의 session은 null입니다. confirmation_required가 true이면 이메일 확인을 마친 뒤 로그인합니다.

const login = await client.auth.opaque.signInWithPassword({
  email: 'user@example.com',
  password: 'user-supplied-password',
})
if (login.error) throw login.error

const { session, key_id, session_key, export_key } = login.data

session_key는 클라이언트와 서버가 각각 도출하는 공유 키입니다. 서버에서는 Server.WithOpaqueSessionKey(...)로 사용할 수 있습니다. export_key는 클라이언트만 보유하며, 두 키 모두 JWT나 SDK 세션 저장소에 넣지 않습니다. 가입 시 받은 export_key는 로그인 성공 전까지 데이터 암호화에 사용하면 안 됩니다.

기존 계정의 OPAQUE 등록, CAPTCHA, 키 파생·만료, 비밀번호 복구 제약은 OPAQUE 문서를 참고하세요. 이 구현과 암호 라이브러리는 독립 보안 감사를 받지 않았습니다.

호환 범위

Supabase Auth의 HTTP API와 auth.* 스키마 호환을 목표로 하지만, 모든 API가 완전히 호환되는 것은 아닙니다.

Parity 테스트는 실제 GoTrue와 응답을 비교합니다. GitHub Actions 실행 summary에는 테스트 결과로 생성한 지원표가 표시되며, 각 항목을 구현됨·부분 호환·미구현·미검증으로 구분합니다. 의도적인 차이는 deviations.yaml에 기록합니다. SDK와 최신 Supabase·TypeScript의 호환성은 매일 별도 workflow로 검사합니다.

개발

make test       # 빌드, 정적 검사, DB 없는 테스트
make test-db    # PostgreSQL 통합 테스트와 race 검사
make ci         # DB 테스트, OpenAPI 검사, 웹 빌드·검사
make parity     # GoTrue 비교 테스트

테스트는 전용 DB의 데이터를 초기화합니다. 운영 DB를 테스트에 연결하지 마세요. CI에서는 별도 job으로 govulncheck도 실행합니다.

한국어 README가 원본입니다. 수정 후 다음 스크립트로 영어 README를 생성합니다. Node.js 22 이상과 인증된 Claude Code CLI가 필요합니다.

node scripts/translate-readme.mjs

스크립트는 claude -p를 호출합니다. GitHub Actions도 같은 스크립트를 사용합니다. CLI 설치와 workflow secret 설정은 README 번역 자동화를 참고하세요.

문서

  • 사용 사례: 동의 수집, 개인정보 조회, 삭제 요청의 API 예시와 제약
  • OPAQUE: 서버 설정, 인증 흐름, 키 관리
  • SDK: Supabase 클라이언트 연결과 OPAQUE API
  • API 규약: API 변경과 OpenAPI 생성 절차
  • 설계 · 개발 계획: 설계 배경과 구현 계획

make dev의 고정 JWT 비밀과 삭제 유예 0일 정책은 로컬 개발용입니다. 운영 환경에서는 TLS, 영속 키, DB 백업, 메일·SMS 제공자와 접근 권한을 별도로 구성해야 합니다.

라이선스

Apache License 2.0

Documentation

Overview

Package dilion assembles the Dilion server: an embeddable, Supabase Auth compatible authentication engine with a built-in privacy/compliance plane.

Everything replaceable is injected through functional options (project.md §2.4). With no options beyond a database, every component falls back to a working default:

srv, err := dilion.NewServer(dilion.WithDSN(os.Getenv("DILION_DSN")))
if err != nil { ... }
if err := srv.Migrate(ctx); err != nil { ... }
log.Fatal(srv.Start(ctx))

API surfaces mounted by Handler:

/auth/v1/*      Supabase Auth compatible (chi, hand-written JSON contract)
/privacy/v1/*   compliance API (huma, OpenAPI 3.1 generated)
/iam/v1/*       management-plane RBAC API (huma)
/healthz        liveness; /readyz database readiness

Multiple database instances

A Dilion "instance" is a whole isolated database (auth.* + dilion_*). The built-in daemon runs exactly one. Embedders that need several inject a ports.InstanceResolver and select the instance per request themselves — Dilion ships no routing rule and no token↔instance binding:

srv, _ := dilion.NewServer(dilion.WithInstanceResolver(myResolver))
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	id := myTenantLookup(r) // subdomain, header, session, ...
	r = r.WithContext(ports.ContextWithInstance(r.Context(), id))
	srv.Handler().ServeHTTP(w, r)
})

Migrate then migrates every instance the resolver lists, and Start runs the compliance workers per instance.

Index

Constants

View Source
const (
	BeforeSignup      = ports.BeforeSignup
	AfterSignup       = ports.AfterSignup
	TokenClaims       = ports.TokenClaims
	BeforeUserDelete  = ports.BeforeUserDelete
	AfterUserDelete   = ports.AfterUserDelete
	BeforeErasureStep = ports.BeforeErasureStep
	AfterErasure      = ports.AfterErasure
	ConsentChanged    = ports.ConsentChanged
	ConsentReconfirm  = ports.ConsentReconfirm
	PIIReveal         = ports.PIIReveal
)
View Source
const DefaultAddr = ":8787"

DefaultAddr is the listen address used when WithAddr is not given.

View Source
const Version = "0.1.0"

Version is reported in the generated OpenAPI document.

Variables

This section is empty.

Functions

This section is empty.

Types

type HookPoint

type HookPoint = ports.HookPoint

Hook points are re-exported so embedders need not import ports directly.

type Option

type Option func(*config)

Option customises the server. Options are applied in order.

func WithAddr

func WithAddr(addr string) Option

WithAddr sets the listen address used by Start (default ":8787").

func WithAuditSink

func WithAuditSink(s ports.AuditSink) Option

WithAuditSink replaces the Postgres audit sink.

func WithAuthConfig

func WithAuthConfig(c *auth.Config) Option

WithAuthConfig pins the /auth/v1 configuration (internal/auth.Config). Without it the configuration is read from the environment with auth.LoadConfig — DILION_AUTH_* first, the upstream GOTRUE_* names as a fallback — and an invalid value fails NewServer.

func WithAuthorizer

func WithAuthorizer(a ports.Authorizer) Option

WithAuthorizer replaces the built-in management-plane RBAC decision point.

func WithClock

func WithClock(cl ports.Clock) Option

WithClock replaces the time source used by the compliance engine (tests).

func WithConnector

func WithConnector(name string, conn ports.Connector) Option

WithConnector registers an external-system connector under name; privacy destinations of type CONNECTOR reference it by that name (§3.1).

func WithDSN

func WithDSN(dsn string) Option

WithDSN connects a pool from a Postgres connection string. Ignored when WithPool is also given.

func WithHook

func WithHook(p ports.HookPoint, fn ports.HookFunc) Option

WithHook installs a lifecycle hook (§2.4). Hooks run in registration order.

func WithInstanceResolver

func WithInstanceResolver(r ports.InstanceResolver) Option

WithInstanceResolver turns the server multi-instance: every request resolves its database, KMS, policy and PII field definitions from r, keyed by the instance id the embedder puts on the context with ports.ContextWithInstance (typically in its own middleware). Dilion provides no HTTP routing and no token↔instance binding — choosing the instance for a request, and binding credentials to instances, is the embedder's responsibility.

Without this option the server is single-instance: WithPool/WithDSN, WithKMS, WithPolicyYAML and WithPIIFieldsYAML are wrapped in a static resolver under ports.DefaultInstanceID and nothing else changes.

A resolver must return stable results per instance id: constructed per-instance objects are cached for the lifetime of the server.

func WithJWTSecret

func WithJWTSecret(secret []byte) Option

WithJWTSecret sets the HS256 signing secret for the compat auth surface. Without it an ephemeral secret is generated and all tokens die on restart.

func WithKMS

func WithKMS(k ports.KMS) Option

WithKMS replaces the default AES-256-GCM local KMS (AWS KMS, Vault, HSM...).

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger sets the base slog logger (default slog.Default()).

func WithMailer

func WithMailer(m ports.Mailer) Option

WithMailer replaces the dev logging mailer.

func WithMasterKey

func WithMasterKey(key []byte) Option

WithMasterKey sets the 32-byte KEK for the default local KMS. Without it an ephemeral key is generated and stored PII becomes unreadable on restart.

func WithPIIFieldsYAML

func WithPIIFieldsYAML(b []byte) Option

WithPIIFieldsYAML pins the PII vault's field definitions:

pii-fields:
  email:     {hint: EMAIL}
  full_name: {hint: NAME}

With definitions in place a profile write may only use the defined keys, its `hint` may be omitted, and a supplied hint must equal the defined one; the stored hint always comes from the definition. Without them (the default) fields stay free-form. Invalid definitions fail NewServer.

func WithPolicyYAML

func WithPolicyYAML(b []byte) Option

WithPolicyYAML supplies compliance policy overrides merged onto the built-in policies (§2.8). Invalid policy data fails NewServer.

func WithPool

func WithPool(p *pgxpool.Pool) Option

WithPool injects an existing pool. The caller keeps ownership: Close does not close a pool it did not create.

func WithSMS

func WithSMS(s ports.SMSSender) Option

WithSMS replaces the dev logging SMS sender.

func WithoutAdminMFA

func WithoutAdminMFA() Option

WithoutAdminMFA disables the MFA requirement on the management plane. Development only — it removes a control required by the security baselines in project.md §8; a warning is logged at startup whenever it is set.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server is a fully wired Dilion deployment. It serves one instance by default and any number of them when WithInstanceResolver is given.

func NewServer

func NewServer(opts ...Option) (*Server, error)

NewServer wires the server. It connects the database (unless WithPool was given), fills in defaults for every injectable component, validates the policy data and builds the HTTP surface.

func (*Server) Close

func (s *Server) Close() error

Close releases resources owned by the server (the pool, when NewServer created it). A pool passed with WithPool is left alone.

func (*Server) Handler

func (s *Server) Handler() http.Handler

Handler returns the HTTP handler, for embedding into an existing server.

func (*Server) Migrate

func (s *Server) Migrate(ctx context.Context) error

Migrate applies pending schema migrations to every instance the resolver lists. Safe to run concurrently with other processes and on every boot: each database's run is serialised by a Postgres advisory lock.

func (*Server) Pool

func (s *Server) Pool() *pgxpool.Pool

Pool exposes the database pool for embedders sharing it with their app. It is the pool given with WithPool/WithDSN and is nil in a deployment configured with WithInstanceResolver alone — use PoolFor there.

func (*Server) PoolFor

func (s *Server) PoolFor(ctx context.Context) (*pgxpool.Pool, error)

PoolFor returns the pool of the instance selected on ctx.

func (*Server) Start

func (s *Server) Start(ctx context.Context) error

Start serves HTTP and runs the compliance background workers until ctx is cancelled, then shuts down gracefully. It returns nil on a clean shutdown.

func (*Server) WithOpaqueSessionKey

func (s *Server) WithOpaqueSessionKey(ctx context.Context, bearer, keyID string, fn func([]byte) error) error

WithOpaqueSessionKey lends the shared OPAQUE key to a short, synchronous operation after checking the bearer, live session, MFA and key lifetime. Select the trusted instance on ctx just as for HTTP requests. fn must not retain the slice, perform network I/O, or recursively update auth state: account/session locks remain held, and the slice is wiped on return.

Directories

Path Synopsis
cmd
dilion command
Command dilion runs the Dilion server: Supabase Auth compatible auth plus the privacy/compliance plane.
Command dilion runs the Dilion server: Supabase Auth compatible auth plus the privacy/compliance plane.
genkey command
Command genkey mints an ES256 (P-256) signing key for the /auth/v1 surface.
Command genkey mints an ES256 (P-256) signing key for the /auth/v1 surface.
openapi command
Command openapi generates web/openapi.yaml from the huma route definitions.
Command openapi generates web/openapi.yaml from the huma route definitions.
Package httpapi implements the API conventions from docs/api-conventions.md: resource IDs, cursor pagination envelope, and problem-details error codes.
Package httpapi implements the API conventions from docs/api-conventions.md: resource IDs, cursor pagination envelope, and problem-details error codes.
internal
api
Package api implements the Dilion management-plane HTTP surface (/privacy/v1 and /iam/v1) with huma v2, following docs/api-conventions.md.
Package api implements the Dilion management-plane HTTP surface (/privacy/v1 and /iam/v1) with huma v2, following docs/api-conventions.md.
audit
Package audit implements the default append-only audit sink (project.md §5).
Package audit implements the default append-only audit sink (project.md §5).
auth
Package auth implements the Supabase Auth (gotrue) compatible surface mounted at /auth/v1 (project.md §2.2, §2.3).
Package auth implements the Supabase Auth (gotrue) compatible surface mounted at /auth/v1 (project.md §2.2, §2.3).
auth/upstreamspec
Package upstreamspec holds Go types generated from the OpenAPI document that github.com/supabase/auth (GoTrue) publishes for its own REST API.
Package upstreamspec holds Go types generated from the OpenAPI document that github.com/supabase/auth (GoTrue) publishes for its own REST API.
devmail
Package devmail provides the development-default ports.Mailer and ports.SMSSender: they deliver nothing and log the message with log/slog.
Package devmail provides the development-default ports.Mailer and ports.SMSSender: they deliver nothing and log the message with log/slog.
hooks
Package hooks implements the hook registry (project.md §2.4).
Package hooks implements the hook registry (project.md §2.4).
iam
Package iam implements the management-plane RBAC of project.md §2.11: flat permission strings bundled into roles, history-preserving role assignments (grant/revoke rows are never deleted — 안전성 확보조치 기준 제5조), scoped API keys, and the default Authorizer.
Package iam implements the management-plane RBAC of project.md §2.11: flat permission strings bundled into roles, history-preserving role assignments (grant/revoke rows are never deleted — 안전성 확보조치 기준 제5조), scoped API keys, and the default Authorizer.
instances
Package instances resolves the per-request Dilion instance.
Package instances resolves the per-request Dilion instance.
kmslocal
Package kmslocal is Dilion's default ports.KMS: AES-256-GCM envelope encryption with per-(subject, scope) data keys stored in dilion_pii.subject_keys, wrapped by a master key held in process memory.
Package kmslocal is Dilion's default ports.KMS: AES-256-GCM envelope encryption with per-(subject, scope) data keys stored in dilion_pii.subject_keys, wrapped by a master key held in process memory.
privacy
Package privacy: engine implementation of the Service contract in service.go.
Package privacy: engine implementation of the Service contract in service.go.
store
Package store owns the Postgres connection pool and the migration runner.
Package store owns the Postgres connection pool and the migration runner.
Package migrations holds Dilion's forward-only SQL migrations as an embedded filesystem.
Package migrations holds Dilion's forward-only SQL migrations as an embedded filesystem.
Package ports defines the shared interfaces (contracts) between Dilion domains and embedder-injectable components.
Package ports defines the shared interfaces (contracts) between Dilion domains and embedder-injectable components.
test
parity
Package parity is Dilion's differential parity + coverage test harness: it runs the Dilion /auth/v1 surface side-by-side against the real upstream github.com/supabase/auth (GoTrue) and asserts behavioural equivalence, excluding the documented intentional deviations (deviations.yaml).
Package parity is Dilion's differential parity + coverage test harness: it runs the Dilion /auth/v1 surface side-by-side against the real upstream github.com/supabase/auth (GoTrue) and asserts behavioural equivalence, excluding the documented intentional deviations (deviations.yaml).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL