Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/frontend
diff options
context:
space:
mode:
authorLeitwolf112026-08-20 20:49:49 +0300
committerLeitwolf112026-08-20 20:49:49 +0300
commit9bc662fefa4dad26274d18bbde7a48cf6ec78d19 (patch)
treed60ff141c6e7b38f94477552540699e102011d01 /frontend
parentb3a82aa98d1edb2570c1ca441be40e891b6f4be3 (diff)
downloadwebsite-9bc662fefa4dad26274d18bbde7a48cf6ec78d19.tar.gz
Add verified publisher usernames to registry signup
Insert a dedicated username step after the password screen and require the same publisher identity after Google authorization. Enforce an uppercase ASCII first letter, ASCII alphanumeric content, 8-128 character bounds, and reserved Progmasoft and Leitwolf names in the backend rather than trusting browser validation. Preserve canonical case while enforcing case-insensitive ownership through a normalized unique PostgreSQL index. Claim the name only after email-code validation and commit code consumption plus ownership atomically so unverified accounts cannot squat publisher coordinates. Expose the canonical publisher name on the account model, add the EF migration and xUnit policy coverage, version the cached auth flow, document the contract, and run backend tests in CI.
Diffstat (limited to 'frontend')
-rw-r--r--frontend/public/scripts/auth-flow.v3.js (renamed from frontend/public/scripts/auth-flow.v2.js)35
-rw-r--r--frontend/src/components/AuthPanel.astro30
2 files changed, 57 insertions, 8 deletions
diff --git a/frontend/public/scripts/auth-flow.v2.js b/frontend/public/scripts/auth-flow.v3.js
index f8b9c69..6f847c8 100644
--- a/frontend/public/scripts/auth-flow.v2.js
+++ b/frontend/public/scripts/auth-flow.v3.js
@@ -6,13 +6,18 @@ const root = document.querySelector("[data-auth-flow]");
const form = root?.querySelector("[data-auth-form]");
const emailInput = root?.querySelector("#auth-email");
const passwordInput = root?.querySelector("#auth-password");
+const publisherNameInput = root?.querySelector("#auth-publisher-name");
const codeInput = root?.querySelector("#auth-code");
const newPasswordInput = root?.querySelector("#auth-new-password");
const alert = root?.querySelector("[data-auth-alert]");
const mode = root?.dataset.mode;
const googleButton = root?.querySelector("[data-google]");
+const publisherBack = root?.querySelector("[data-publisher-back]");
let step = "email";
let email = "";
+let registrationPassword = "";
+let publisherName = "";
+let googleRegistration = false;
let recoveryCode = "";
function showAlert(message, error = false) {
@@ -54,6 +59,7 @@ async function explainFailure(response) {
}
root?.querySelector("[data-back]")?.addEventListener("click", () => showStep("email"));
+publisherBack?.addEventListener("click", () => showStep("password"));
root?.querySelector("[data-resend]")?.addEventListener("click", async () => {
const path = mode === "recover" ? "recovery/start" : "verify-email/resend";
const response = await request(path, { email });
@@ -86,8 +92,10 @@ function initializeGoogleVerification() {
emailInput.value = requestedEmail;
if (!emailInput.checkValidity()) return;
email = requestedEmail;
- showStep("code");
- showAlert("Google sign-in succeeded. Check your email for the Visual X# verification code.");
+ googleRegistration = true;
+ if (publisherBack) publisherBack.hidden = true;
+ showStep("publisher");
+ showAlert("Google sign-in succeeded. Choose your username to continue account verification.");
window.history.replaceState({}, "", window.location.pathname);
}
@@ -119,10 +127,8 @@ form?.addEventListener("submit", async (event) => {
return;
}
if (mode === "register") {
- const response = await request("register", { email, password });
- if (!response.ok) return explainFailure(response);
- showStep("code");
- showAlert("Check your email to verify this account.");
+ registrationPassword = password;
+ showStep("publisher");
return;
}
const response = await request("login", { email, password });
@@ -131,6 +137,21 @@ form?.addEventListener("submit", async (event) => {
return;
}
+ if (step === "publisher") {
+ publisherName = publisherNameInput?.value ?? "";
+ if (!publisherNameInput?.checkValidity()) {
+ publisherNameInput?.reportValidity();
+ return;
+ }
+ if (!googleRegistration) {
+ const response = await request("register", { email, password: registrationPassword, publisherName });
+ if (!response.ok) return explainFailure(response);
+ }
+ showStep("code");
+ showAlert("Check your email to verify this account.");
+ return;
+ }
+
if (step === "code") {
const code = codeInput?.value.trim().toUpperCase() ?? "";
if (!codeInput?.checkValidity()) {
@@ -142,7 +163,7 @@ form?.addEventListener("submit", async (event) => {
showStep("new-password");
return;
}
- const response = await request("verify-email", { email, code });
+ const response = await request("verify-email", { email, code, publisherName });
if (!response.ok) return explainFailure(response);
window.location.assign("https://viget.xsharp-lang.xyz/dashboard/");
return;
diff --git a/frontend/src/components/AuthPanel.astro b/frontend/src/components/AuthPanel.astro
index 169c55a..3c537aa 100644
--- a/frontend/src/components/AuthPanel.astro
+++ b/frontend/src/components/AuthPanel.astro
@@ -104,6 +104,34 @@ const content = copy[mode];
}
</div>
+ {
+ mode === "register" && (
+ <div class="auth-step" data-step="publisher" hidden>
+ <button class="auth-back" type="button" data-publisher-back>← Change password</button>
+ <span class="auth-email-value" data-email-value></span>
+ <label for="auth-publisher-name">Username</label>
+ <input
+ id="auth-publisher-name"
+ name="publisher-name"
+ type="text"
+ autocomplete="username"
+ autocapitalize="none"
+ spellcheck="false"
+ pattern="[A-Z][A-Za-z0-9]{7,127}"
+ minlength="8"
+ maxlength="128"
+ placeholder="MyPublisher"
+ required
+ />
+ <p class="field-hint">
+ Use 8–128 ASCII letters or digits and start with an uppercase letter. <code>Progmasoft</code> and{" "}
+ <code>Leitwolf</code> are reserved.
+ </p>
+ <button class="auth-submit" type="submit">Continue</button>
+ </div>
+ )
+ }
+
<div class="auth-step" data-step="code" hidden>
<span class="auth-email-value" data-code-email></span>
<label for="auth-code">Verification code</label>
@@ -147,4 +175,4 @@ const content = copy[mode];
</section>
</main>
-<script is:inline type="module" src="/scripts/auth-flow.v2.js"></script>
+<script is:inline type="module" src="/scripts/auth-flow.v3.js"></script>