Add admin invites and user workspace management tools.

Implement invite-token registration with optional email delivery, add admin UI actions for creating invites and opening user workspaces, and support superuser workspace override while preserving per-user code isolation with shared read-only lib.

Made-with: Cursor
This commit is contained in:
2026-05-01 21:13:13 +12:00
parent e4c811f51d
commit 7d682cce8d
15 changed files with 683 additions and 71 deletions

View File

@@ -61,11 +61,16 @@
<input id="username" name="username" autocomplete="username" required />
<label for="password">Password</label>
<input id="password" type="password" name="password" autocomplete="new-password" required />
<input id="invite-token" type="hidden" name="invite_token" />
<button type="submit" id="submit">Register</button>
</form>
<p><a href="/login">Sign in</a> · <a href="/">Home</a></p>
</main>
<script>
const inviteToken = new URLSearchParams(window.location.search).get("invite") || "";
const inviteInput = document.getElementById("invite-token");
if (inviteInput) inviteInput.value = inviteToken;
(async function checkStatus() {
try {
const r = await fetch("/api/auth/status");
@@ -73,9 +78,12 @@
if (!s.auth_enabled) {
document.getElementById("err").textContent = "Registration is disabled (AUTH_ENABLED is not set).";
document.getElementById("form").style.display = "none";
} else if (!s.register_open) {
} else if (!s.register_open && !inviteToken) {
document.getElementById("err").textContent = "Public registration is closed. Ask an administrator.";
document.getElementById("form").style.display = "none";
} else if (s.invite_required && !inviteToken) {
document.getElementById("err").textContent = "This server requires an invite link to register.";
document.getElementById("form").style.display = "none";
}
} catch (_e) {}
})();
@@ -90,6 +98,7 @@
const body = {
username: document.getElementById("username").value.trim(),
password: document.getElementById("password").value,
invite_token: inviteToken || null,
};
const res = await fetch("/api/auth/register", {
method: "POST",