[ci skip] Flatten module wrappers into stack roots
Remove the module "xxx" { source = "./module" } indirection layer
from all 66 service stacks. Resources are now defined directly in
each stack's main.tf instead of through a wrapper module.
- Merge module/main.tf contents into stack main.tf
- Apply variable replacements (var.tier -> local.tiers.X, renamed vars)
- Fix shared module paths (one fewer ../ at each level)
- Move extra files/dirs (factory/, chart_values, subdirs) to stack root
- Update state files to strip module.<name>. prefix
- Update CLAUDE.md to reflect flat structure
Verified: terragrunt plan shows 0 add, 0 destroy across all stacks.
This commit is contained in:
parent
b0499a7f31
commit
c7c7047f1c
245 changed files with 11733 additions and 12432 deletions
|
|
@ -1,219 +0,0 @@
|
|||
// WebAuthn helper: base64url encode/decode
|
||||
function bufToBase64url(buf) {
|
||||
const bytes = new Uint8Array(buf);
|
||||
let str = '';
|
||||
for (const b of bytes) str += String.fromCharCode(b);
|
||||
return btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
||||
}
|
||||
|
||||
function base64urlToBuf(b64) {
|
||||
const pad = b64.length % 4;
|
||||
if (pad) b64 += '='.repeat(4 - pad);
|
||||
const str = atob(b64.replace(/-/g, '+').replace(/_/g, '/'));
|
||||
const buf = new Uint8Array(str.length);
|
||||
for (let i = 0; i < str.length; i++) buf[i] = str.charCodeAt(i);
|
||||
return buf.buffer;
|
||||
}
|
||||
|
||||
let currentUser = null;
|
||||
|
||||
function showAuthDialog() {
|
||||
document.getElementById('auth-dialog').showModal();
|
||||
}
|
||||
|
||||
function switchAuthTab(tab, evt) {
|
||||
const btns = document.querySelectorAll('.dialog-tab-btn');
|
||||
btns.forEach(b => b.classList.remove('active'));
|
||||
evt.target.classList.add('active');
|
||||
|
||||
document.getElementById('auth-login-form').style.display = tab === 'login' ? 'block' : 'none';
|
||||
document.getElementById('auth-register-form').style.display = tab === 'register' ? 'block' : 'none';
|
||||
document.getElementById('login-error').textContent = '';
|
||||
document.getElementById('register-error').textContent = '';
|
||||
}
|
||||
|
||||
async function doRegister() {
|
||||
const username = document.getElementById('register-username').value.trim();
|
||||
const errEl = document.getElementById('register-error');
|
||||
errEl.textContent = '';
|
||||
|
||||
if (!username || username.length < 3) {
|
||||
errEl.textContent = 'Username must be at least 3 characters';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Step 1: Begin registration
|
||||
const beginResp = await fetch('/api/auth/register/begin', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username })
|
||||
});
|
||||
|
||||
if (!beginResp.ok) {
|
||||
const err = await beginResp.json();
|
||||
errEl.textContent = err.error || 'Registration failed';
|
||||
return;
|
||||
}
|
||||
|
||||
const options = await beginResp.json();
|
||||
|
||||
// Convert base64url fields to ArrayBuffers
|
||||
options.publicKey.challenge = base64urlToBuf(options.publicKey.challenge);
|
||||
options.publicKey.user.id = base64urlToBuf(options.publicKey.user.id);
|
||||
if (options.publicKey.excludeCredentials) {
|
||||
options.publicKey.excludeCredentials = options.publicKey.excludeCredentials.map(c => ({
|
||||
...c,
|
||||
id: base64urlToBuf(c.id)
|
||||
}));
|
||||
}
|
||||
|
||||
// Step 2: Create credential via browser
|
||||
const credential = await navigator.credentials.create(options);
|
||||
|
||||
// Step 3: Finish registration
|
||||
const attestation = {
|
||||
id: credential.id,
|
||||
rawId: bufToBase64url(credential.rawId),
|
||||
type: credential.type,
|
||||
response: {
|
||||
attestationObject: bufToBase64url(credential.response.attestationObject),
|
||||
clientDataJSON: bufToBase64url(credential.response.clientDataJSON)
|
||||
}
|
||||
};
|
||||
|
||||
const finishResp = await fetch(`/api/auth/register/finish?username=${encodeURIComponent(username)}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(attestation)
|
||||
});
|
||||
|
||||
if (!finishResp.ok) {
|
||||
const err = await finishResp.json();
|
||||
errEl.textContent = err.error || 'Registration failed';
|
||||
return;
|
||||
}
|
||||
|
||||
const user = await finishResp.json();
|
||||
setLoggedIn(user);
|
||||
document.getElementById('auth-dialog').close();
|
||||
} catch (e) {
|
||||
console.error('Registration error:', e);
|
||||
errEl.textContent = e.message || 'Registration failed';
|
||||
}
|
||||
}
|
||||
|
||||
async function doLogin() {
|
||||
const username = document.getElementById('login-username').value.trim();
|
||||
const errEl = document.getElementById('login-error');
|
||||
errEl.textContent = '';
|
||||
|
||||
if (!username) {
|
||||
errEl.textContent = 'Username required';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Step 1: Begin login
|
||||
const beginResp = await fetch('/api/auth/login/begin', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username })
|
||||
});
|
||||
|
||||
if (!beginResp.ok) {
|
||||
const err = await beginResp.json();
|
||||
errEl.textContent = err.error || 'Login failed';
|
||||
return;
|
||||
}
|
||||
|
||||
const options = await beginResp.json();
|
||||
|
||||
// Convert base64url fields
|
||||
options.publicKey.challenge = base64urlToBuf(options.publicKey.challenge);
|
||||
if (options.publicKey.allowCredentials) {
|
||||
options.publicKey.allowCredentials = options.publicKey.allowCredentials.map(c => ({
|
||||
...c,
|
||||
id: base64urlToBuf(c.id)
|
||||
}));
|
||||
}
|
||||
|
||||
// Step 2: Get assertion via browser
|
||||
const assertion = await navigator.credentials.get(options);
|
||||
|
||||
// Step 3: Finish login
|
||||
const assertionData = {
|
||||
id: assertion.id,
|
||||
rawId: bufToBase64url(assertion.rawId),
|
||||
type: assertion.type,
|
||||
response: {
|
||||
authenticatorData: bufToBase64url(assertion.response.authenticatorData),
|
||||
clientDataJSON: bufToBase64url(assertion.response.clientDataJSON),
|
||||
signature: bufToBase64url(assertion.response.signature),
|
||||
userHandle: assertion.response.userHandle ? bufToBase64url(assertion.response.userHandle) : ''
|
||||
}
|
||||
};
|
||||
|
||||
const finishResp = await fetch(`/api/auth/login/finish?username=${encodeURIComponent(username)}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(assertionData)
|
||||
});
|
||||
|
||||
if (!finishResp.ok) {
|
||||
const err = await finishResp.json();
|
||||
errEl.textContent = err.error || 'Login failed';
|
||||
return;
|
||||
}
|
||||
|
||||
const user = await finishResp.json();
|
||||
setLoggedIn(user);
|
||||
document.getElementById('auth-dialog').close();
|
||||
} catch (e) {
|
||||
console.error('Login error:', e);
|
||||
errEl.textContent = e.message || 'Login failed';
|
||||
}
|
||||
}
|
||||
|
||||
async function doLogout() {
|
||||
await fetch('/api/auth/logout', { method: 'POST' });
|
||||
setLoggedOut();
|
||||
}
|
||||
|
||||
function setLoggedIn(user) {
|
||||
currentUser = user;
|
||||
const section = document.getElementById('auth-section');
|
||||
section.innerHTML = `
|
||||
<span>Hi, ${escapeHtml(user.username)}</span>
|
||||
<button onclick="doLogout()">Logout</button>
|
||||
`;
|
||||
document.getElementById('tab-mine').classList.remove('hidden');
|
||||
if (user.is_admin) {
|
||||
document.getElementById('tab-admin').classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function setLoggedOut() {
|
||||
currentUser = null;
|
||||
const section = document.getElementById('auth-section');
|
||||
section.innerHTML = '<button id="login-btn" onclick="showAuthDialog()">Login / Register</button>';
|
||||
document.getElementById('tab-mine').classList.add('hidden');
|
||||
document.getElementById('tab-admin').classList.add('hidden');
|
||||
// Switch to streams tab if on a protected tab
|
||||
const activeTab = document.querySelector('.tab-btn.active');
|
||||
if (activeTab && (activeTab.dataset.tab === 'mine' || activeTab.dataset.tab === 'admin')) {
|
||||
switchTab('streams');
|
||||
}
|
||||
}
|
||||
|
||||
async function checkAuth() {
|
||||
try {
|
||||
const resp = await fetch('/api/auth/me');
|
||||
if (resp.ok) {
|
||||
const user = await resp.json();
|
||||
setLoggedIn(user);
|
||||
}
|
||||
} catch (e) {
|
||||
// Not logged in
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue