Files
AriWeb-Suite/modules/webmaker/public/login.html
T

111 lines
7.8 KiB
HTML

<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>로그인 — AI Webmaker</title>
<link rel="stylesheet" href="/style.css">
<style>
body { display: grid; place-items: center; min-height: 100vh; }
.auth { width: 380px; max-width: calc(100vw - 32px); }
.auth h1 { text-align: center; margin-bottom: 4px; }
.auth .sub { text-align: center; margin-bottom: 16px; }
.tabs { display: flex; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; margin-bottom: 14px; }
.tabs button { flex: 1; border: 0; border-radius: 0; padding: 9px; background: #fff; color: var(--muted); font-weight: 600; }
.tabs button.active { background: var(--accent); color: #fff; }
.msg { min-height: 18px; font-size: 12px; color: var(--warn); margin: 6px 0 10px; }
.notice { font-size: 13px; background: #fff7ed; border: 1px solid #fed7aa; color: #9a3412; border-radius: 8px; padding: 8px 10px; margin-bottom: 12px; }
</style>
<link rel="stylesheet" href="/theme.css"><!-- v1.2.0: 관리자 콘솔에서 정한 화면 색상·외형 -->
</head>
<body>
<div class="card auth">
<div class="lang-switch small" style="display:none;text-align:right"><select id="langSelect" title="Language"></select></div>
<h1>AI Webmaker</h1>
<div class="sub">로그인하면 내 사이트만 보이고, 생성·수정 이력이 Gitea 에 보관됩니다.</div>
<!-- v1.5.11 (요구 1): 기본 비밀번호(admin/admin)로 로그인한 관리자는 다른 화면으로 가기 전에 여기서 비밀번호를 바꾼다 (server.js 가 /login?change=1 로 보낸다) -->
<div class="notice hidden" id="changeNotice">기본 비밀번호로 로그인했습니다. 계속하려면 먼저 새 비밀번호를 정하세요.</div>
<div class="tabs" id="tabs">
<button id="tabLogin" class="active" type="button">로그인</button>
<button id="tabSignup" type="button">회원가입</button>
</div>
<form id="form" autocomplete="on">
<label>아이디
<input id="uid" name="username" autocomplete="username" placeholder="영문 소문자·숫자 3~32자" required>
</label>
<label>비밀번호
<input id="upw" name="password" type="password" autocomplete="current-password" placeholder="6자 이상" data-pw-rule="placeholder" required>
</label>
<label id="nameRow" class="hidden">이름 (회사명)
<input id="uname" name="name" autocomplete="organization" placeholder="예: 오즈 그룹">
</label>
<div class="msg" id="msg"></div>
<button id="submit" class="primary" type="submit">로그인</button>
</form>
<!-- 비밀번호 변경 폼 (mustChangePassword) -->
<form id="changeForm" class="hidden" autocomplete="on">
<label>현재 비밀번호
<input id="cpw" type="password" autocomplete="current-password" required>
</label>
<label>새 비밀번호
<input id="npw" type="password" autocomplete="new-password" placeholder="6자 이상" data-pw-rule="placeholder" required>
</label>
<label>새 비밀번호 확인
<input id="npw2" type="password" autocomplete="new-password" required>
</label>
<div class="msg" id="changeMsg"></div>
<button id="changeSubmit" class="primary" type="submit">비밀번호 바꾸고 시작</button>
<p class="muted small" style="text-align:center;margin-top:10px"><a href="#" id="changeLogout">다른 계정으로 로그인</a></p>
</form>
<p class="muted small" id="signupHint" style="text-align:center;margin-top:12px">계정이 없으면 <a href="#" id="toSignup">회원가입</a>으로 바로 만들 수 있습니다.</p>
<p class="muted small hidden" id="signupClosed" style="text-align:center;margin-top:12px">회원가입은 닫혀 있습니다. 관리자에게 계정을 요청하세요.</p>
</div>
<script src="/i18n.js"></script>
<script src="/pwrule.js"></script>
<script>
const $ = (id) => document.getElementById(id);
let mode = 'login';
function setMode(m) {
mode = m;
$('tabLogin').classList.toggle('active', m === 'login'); $('tabSignup').classList.toggle('active', m === 'signup');
$('nameRow').classList.toggle('hidden', m !== 'signup'); $('uname').required = m === 'signup';
$('upw').autocomplete = m === 'signup' ? 'new-password' : 'current-password';
$('submit').textContent = m === 'signup' ? '회원가입 후 시작' : '로그인'; $('msg').textContent = '';
}
$('tabLogin').onclick = () => setMode('login'); $('tabSignup').onclick = () => setMode('signup');
$('toSignup').onclick = (e) => { e.preventDefault(); setMode('signup'); $('uid').focus(); };
/** 로그인 뒤 갈 곳: 관리자는 콘솔, 그 밖은 빌더. 비밀번호를 바꿔야 하는 계정은 변경 폼 */
function go(user) { if (user && user.mustChangePassword) return showChange(); location.href = user && user.role === 'admin' ? '/admin' : '/'; }
function showChange() { $('changeNotice').classList.remove('hidden'); $('tabs').classList.add('hidden'); $('form').classList.add('hidden'); $('signupHint').classList.add('hidden'); $('signupClosed').classList.add('hidden'); $('changeForm').classList.remove('hidden'); $('cpw').focus(); }
$('form').onsubmit = async (e) => {
e.preventDefault(); $('submit').disabled = true; $('msg').textContent = '';
try {
const r = await fetch(mode === 'signup' ? '/api/auth/signup' : '/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: $('uid').value, password: $('upw').value, name: $('uname').value }) });
const b = await r.json().catch(() => ({}));
if (!r.ok) throw new Error(b.error || (r.status === 403 && mode === 'signup' ? '회원가입이 닫혀 있습니다 (HTTP 403)' : `HTTP ${r.status}`));
// v1.5.27 (요구 6): 관리자 승인 방식이면 가입은 "신청 접수" 로 끝난다 — 세션 없음, 안내만
if (r.status === 202 && b.pendingApproval) { $('msg').style.color = 'var(--ok)'; $('msg').textContent = b.message || '가입 신청이 접수되었습니다. 관리자가 승인하면 로그인할 수 있습니다'; setMode('login'); $('msg').textContent = b.message || '가입 신청이 접수되었습니다. 관리자가 승인하면 로그인할 수 있습니다'; $('submit').disabled = false; return; }
go(b.user);
} catch (err) { $('msg').textContent = err.message; $('submit').disabled = false; }
};
$('changeForm').onsubmit = async (e) => {
e.preventDefault(); $('changeMsg').textContent = '';
if ($('npw').value !== $('npw2').value) { $('changeMsg').textContent = '새 비밀번호 확인이 다릅니다'; return; }
$('changeSubmit').disabled = true;
try {
const r = await fetch('/api/auth/password', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ current: $('cpw').value, next: $('npw').value }) });
const b = await r.json().catch(() => ({}));
if (!r.ok) throw new Error(b.error || `HTTP ${r.status}`);
location.href = b.user && b.user.role === 'admin' ? '/admin' : '/';
} catch (err) { $('changeMsg').textContent = err.message; $('changeSubmit').disabled = false; }
};
$('changeLogout').onclick = async (e) => { e.preventDefault(); await fetch('/api/auth/logout', { method: 'POST' }); location.href = '/login?direct=1'; };
// v1.5.11: ?change=1 (서버가 보낸 경우) 이면 로그인 상태를 확인해 변경 폼을 바로 연다. 회원가입이 닫혀 있으면 탭·안내를 숨긴다
(async () => {
try { const r = await fetch('/api/auth/info'); const b = await r.json(); if (b && b.signupOpen === false) { $('tabSignup').classList.add('hidden'); $('signupHint').classList.add('hidden'); $('signupClosed').classList.remove('hidden'); } } catch (_) {}
if (new URL(location.href).searchParams.get('change') === '1') { try { const r = await fetch('/api/auth/me'); const b = await r.json(); if (r.ok && b.user && b.user.mustChangePassword) showChange(); } catch (_) {} }
})();
</script>
</body>
</html>