포털 AriMail 설정 Host 시도 되돌림: Node fetch 는 Host 를 바꿀 수 없어 setup_url 이 기본 도메인 이름이어야 함을 문서화

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
zzabisagent-lab
2026-10-26 14:19:09 +00:00
co-authored by Claude Fable 5.1
parent 5e74a5a938
commit 65f8102b5f
5 changed files with 3950 additions and 192 deletions
+5 -5
View File
@@ -38,8 +38,8 @@ export function create(deps) {
const list = await api.domains(s.id); const d = list.find((x) => x.domain === domain); const host = d ? String(d.hosts[0]).split('/')[0] : `mail.${domain}`;
hostCache.set(s.id + '|' + domain, { host, at: Date.now() }); return host;
}
/** 전체 설정 API 호출: AriMail 은 기본 도메인 호스트(setup_host, 예: mail.arihakdang.com)에서만 설정 API 를 응답하므로 URL 은 setup_url(인증서 이름·내부 주소)로 두고 Host 헤더를 따로 보낸다. */
const setupCall = (s, path, opts = {}) => call(fetchImpl, setupOf(s) + path, { ...opts, host: s.setup_host || opts.host || null, headers: { ...setupHeaders(s), ...(opts.headers || {}) } });
/** 전체 설정 API 호출. AriMail 은 설정 API 를 기본 도메인 호스트(req.headers.host)에서만 응답하고 Node fetch 는 Host 헤더를 바꿀 수 없으므로 setup_url 자체가 기본 도메인 이름이어야 한다(등록 화면 안내, 실제 환경 확인). */
const setupCall = (s, path, opts = {}) => call(fetchImpl, setupOf(s) + path, { ...opts, headers: { ...setupHeaders(s), ...(opts.headers || {}) } });
const setupHeaders = (s) => { const tok = vault.get(secretName(s.id, 'setup-token')); if (!tok) { const e = new Error('AriMail 전체 설정 토큰이 보관소에 없습니다'); e.code = 'mail_server_token_missing'; throw e; } return { 'X-Setup-Token': tok }; };
/** 관리자 세션 쿠키(도메인별). 보관소의 관리자 로그인으로 로그인해 10분 캐시. */
const adminCache = new Map();
@@ -59,12 +59,12 @@ export function create(deps) {
defaultServer() { return serverRow(null); },
getServer: (id) => serverRow(id),
/** 서버 등록·수정. secrets 는 보관소에만 저장하고 응답에 돌려주지 않는다. */
saveServer({ id = null, name, baseUrl, internalUrl = null, setupUrl = null, setupHost = null, mailHost = null, isDefault = false, secrets = {} }) {
saveServer({ id = null, name, baseUrl, internalUrl = null, setupUrl = null, mailHost = null, isDefault = false, secrets = {} }) {
const now = nowIso(); const sid = id || newId('ms_');
if (!/^https?:\/\//.test(String(baseUrl || ''))) throw Object.assign(new Error('공개 주소는 http(s):// 로 시작해야 합니다'), { code: 'validation', status: 422 });
if (isDefault) store.run('UPDATE mail_servers SET is_default=0');
if (id) store.run('UPDATE mail_servers SET name=?, base_url=?, internal_url=?, setup_url=?, setup_host=?, mail_host=?, is_default=?, updated_at=? WHERE id=?', name, baseUrl, internalUrl, setupUrl, setupHost, mailHost, isDefault ? 1 : 0, now, id);
else store.run('INSERT INTO mail_servers(id, name, base_url, internal_url, setup_url, setup_host, mail_host, is_default, created_at, updated_at) VALUES(?,?,?,?,?,?,?,?,?,?)', sid, name, baseUrl, internalUrl, setupUrl, setupHost, mailHost, isDefault || !store.get('SELECT 1 FROM mail_servers') ? 1 : 0, now, now);
if (id) store.run('UPDATE mail_servers SET name=?, base_url=?, internal_url=?, setup_url=?, mail_host=?, is_default=?, updated_at=? WHERE id=?', name, baseUrl, internalUrl, setupUrl, mailHost, isDefault ? 1 : 0, now, id);
else store.run('INSERT INTO mail_servers(id, name, base_url, internal_url, setup_url, mail_host, is_default, created_at, updated_at) VALUES(?,?,?,?,?,?,?,?,?)', sid, name, baseUrl, internalUrl, setupUrl, mailHost, isDefault || !store.get('SELECT 1 FROM mail_servers') ? 1 : 0, now, now);
for (const [k, v] of Object.entries(secrets)) if (v) vault.set(secretName(sid, k), typeof v === 'string' ? v : JSON.stringify(v), { kind: 'arimail' });
return api.listServers().find((s) => s.id === sid);
},