diff --git a/frontend/src/pages/profile/profile.html b/frontend/src/pages/profile/profile.html
index 6752e23..76c7565 100644
--- a/frontend/src/pages/profile/profile.html
+++ b/frontend/src/pages/profile/profile.html
@@ -1,18 +1,14 @@
Edit Profile
-
-
This is a guest Account
- You can't change anything here
+ This is a guest Account
+ You can't change anything here
-
-
-
+
+ Unknown
-
-
@@ -20,17 +16,18 @@
-
Provider
-
+
+ class="max-w-md p-3 border border-gray-300 rounded-lg overflow-y-auto bg-white text-gray-800 whitespace-pre-wrap">
+
-
+
+ class="max-w-md p-3 border border-gray-300 rounded-lg overflow-y-auto bg-white text-gray-800 whitespace-pre-wrap">
+
@@ -39,54 +36,42 @@
-
+
-
-
-
+
-
+
-
-
+
Two-Factor Authentication (TOTP)
-
Status: Disabled
-
-
+ class="bg-green-600 text-white-700 px-3 py-1 rounded hover:bg-green-700">Enable
-
-
-
diff --git a/frontend/src/pages/profile/profile.ts b/frontend/src/pages/profile/profile.ts
index 39171ce..4b91b84 100644
--- a/frontend/src/pages/profile/profile.ts
+++ b/frontend/src/pages/profile/profile.ts
@@ -6,15 +6,7 @@ import { isNullish } from "@app/utils";
import client from "@app/api";
import QRCode from "qrcode";
-type OAuthQRCodeOptions = {
- label?: string; // e.g. your-app:user@example.com
- issuer?: string; // e.g. "YourApp"
- algorithm?: "SHA1" | "SHA256" | "SHA512";
- digits?: number;
- period?: number;
-};
-
-/**
+/*
* Renders an OAuth2-compatible TOTP QR code into a canvas.
*
* @param canvas HTMLCanvasElement to draw into
@@ -38,219 +30,225 @@ export async function renderOAuth2QRCode(
});
canvas.style.width = "";
canvas.style.height = "";
- function removeBgColor(...elem: HTMLElement[]) {
- for (let e of elem) {
- for (let c of e.classList.values()) {
- if (c.startsWith("bg-") || c.startsWith("hover:bg-"))
- e.classList.remove(c);
- }
+}
+function removeBgColor(...elem: HTMLElement[]) {
+ for (let e of elem) {
+ for (let c of e.classList.values()) {
+ if (c.startsWith("bg-") || c.startsWith("hover:bg-"))
+ e.classList.remove(c);
}
}
+}
- async function route(url: string, _args: { [k: string]: string }) {
- setTitle("Edit Profile");
- return {
- html: page,
- postInsert: async (app: HTMLElement | undefined) => {
- const user = await updateUser();
- if (isNullish(user)) return showError("No User");
- if (isNullish(app)) return showError("Failed to render");
- let totpState = await (async () => {
- let res = await client.statusOtp();
- if (res.kind === "success")
- return {
- enabled:
- (res.msg as string) === "statusOtp.success.enabled",
- secret:
- (res.msg as string) === "statusOtp.success.enabled"
- ? res.payload.secret
- : null,
- };
- else {
- showError("Failed to get OTP status");
- return {
- enabled: false,
- secret: null,
- };
- }
- })();
- // ---- Simulated State ----
- let totpEnabled = totpState.enabled;
- let totpSecret = totpState.secret; // would come from backend
-
- let guestBox = app.querySelector
("#isGuestBox")!;
- let displayNameWrapper = app.querySelector(
- "#displayNameWrapper",
- )!;
- let displayNameBox =
- app.querySelector("#displayNameBox")!;
- let displayNameButton =
- app.querySelector("#displayNameButton")!;
- let loginNameWrapper =
- app.querySelector("#loginNameWrapper")!;
- let loginNameBox =
- app.querySelector("#loginNameBox")!;
- let passwordWrapper =
- app.querySelector("#passwordWrapper")!;
- let passwordBox =
- app.querySelector("#passwordBox")!;
- let passwordButton =
- app.querySelector("#passwordButton")!;
-
- let providerWrapper =
- app.querySelector("#providerWrapper")!;
- let providerNameBox =
- app.querySelector("#providerNameBox")!;
- let providerUserBox =
- app.querySelector("#providerUserBox")!;
-
- let accountTypeBox =
- app.querySelector("#accountType")!;
- displayNameBox.value = user.name;
-
- guestBox.hidden = !user.guest;
-
- // ---- DOM Elements ----
- const totpStatusText = app.querySelector("#totpStatusText")!;
- const enableBtn =
- app.querySelector("#enableTotp")!;
- const disableBtn =
- app.querySelector("#disableTotp")!;
- const showSecretBtn =
- app.querySelector("#showSecret")!;
- const secretBox = app.querySelector("#totpSecretBox")!;
- const secretText =
- app.querySelector("#totpSecretText")!;
- const secretCanvas =
- app.querySelector("#totpSecretCanvas")!;
-
- if (user.guest) {
- for (let c of passwordButton.classList.values()) {
- if (c.startsWith("bg-") || c.startsWith("hover:bg-"))
- passwordButton.classList.remove(c);
- }
- let totpWrapper = app.querySelector("#totpWrapper")!;
-
- if (user.guest) {
- removeBgColor(
- passwordButton,
- displayNameButton,
- enableBtn,
- disableBtn,
- showSecretBtn,
- );
-
- passwordButton.classList.add(
- "bg-gray-700",
- "hover:bg-gray-700",
- );
-
- passwordBox.disabled = true;
- passwordBox.classList.add("color-white");
-
- displayNameButton.disabled = true;
- displayNameButton.classList.add("bg-gray-700", "color-white");
-
- displayNameBox.disabled = true;
- displayNameBox.classList.add("color-white");
- enableBtn.classList.add("bg-gray-700", "hover:bg-gray-700");
- disableBtn.classList.add("bg-gray-700", "hover:bg-gray-700");
- showSecretBtn.classList.add("bg-gray-700", "hover:bg-gray-700");
-
- enableBtn.disabled = true;
- disableBtn.disabled = true;
- showSecretBtn.disabled = true;
-
- accountTypeBox.innerText = "Guest";
- } else if (!isNullish(user.selfInfo?.loginName)) {
- loginNameWrapper.hidden = false;
- loginNameBox.innerText = user.selfInfo.loginName;
-
- accountTypeBox.innerText = "Normal";
- } else if (
- !isNullish(user.selfInfo?.providerId) &&
- !isNullish(user.selfInfo?.providerUser)
- ) {
- providerWrapper.hidden = false;
- providerNameBox.innerText = user.selfInfo.providerId;
- providerUserBox.innerText = user.selfInfo.providerUser;
-
- enableBtn.classList.add("bg-gray-700", "hover:bg-gray-700");
- disableBtn.classList.add("bg-gray-700", "hover:bg-gray-700");
- showSecretBtn.classList.add("bg-gray-700", "hover:bg-gray-700");
-
- enableBtn.disabled = true;
- disableBtn.disabled = true;
- showSecretBtn.disabled = true;
-
- removeBgColor(enableBtn, disableBtn, showSecretBtn);
- passwordWrapper.hidden = true;
- totpWrapper.hidden = true;
-
- accountTypeBox.innerText = "Provider";
- }
-
- // ---- Update UI ----
- function refreshTotpUI() {
- if (totpEnabled) {
- totpStatusText.textContent = "Status: Enabled";
-
- enableBtn.classList.add("hidden");
- disableBtn.classList.remove("hidden");
- showSecretBtn.classList.remove("hidden");
- } else {
- totpStatusText.textContent = "Status: Disabled";
-
- enableBtn.classList.remove("hidden");
- disableBtn.classList.add("hidden");
- showSecretBtn.classList.add("hidden");
- secretBox.classList.add("hidden");
- }
- }
-
- // ---- Button Events ----
- enableBtn.onclick = async () => {
- let res = await client.enableOtp();
- if (res.kind === "success") {
- navigateTo(url);
- } else {
- showError(`failed to activate OTP: ${res.msg}`);
- }
+async function route(url: string, _args: { [k: string]: string }) {
+ setTitle("Edit Profile");
+ return {
+ html: page,
+ postInsert: async (app: HTMLElement | undefined) => {
+ const user = await updateUser();
+ if (isNullish(user)) return showError("No User");
+ if (isNullish(app)) return showError("Failed to render");
+ let totpState = await (async () => {
+ let res = await client.statusOtp();
+ if (res.kind === "success")
+ return {
+ enabled:
+ (res.msg as string) === "statusOtp.success.enabled",
+ secret:
+ (res.msg as string) === "statusOtp.success.enabled"
+ ? res.payload.secret
+ : null,
};
-
- disableBtn.onclick = async () => {
- let res = await client.disableOtp();
- if (res.kind === "success") {
- navigateTo(url);
- } else {
- showError(`failed to deactivate OTP: ${res.msg}`);
- }
+ else {
+ showError("Failed to get OTP status");
+ return {
+ enabled: false,
+ secret: null,
};
+ }
+ })();
+ // ---- Simulated State ----
+ let totpEnabled = totpState.enabled;
+ let totpSecret = totpState.secret; // would come from backend
- showSecretBtn.onclick = () => {
- if (!isNullish(totpSecret)) {
- secretText.textContent = totpSecret;
- renderOAuth2QRCode(secretCanvas, totpSecret);
- }
- secretBox.classList.toggle("hidden");
- };
+ let guestBox = app.querySelector("#isGuestBox")!;
+ let displayNameWrapper = app.querySelector(
+ "#displayNameWrapper",
+ )!;
+ let displayNameBox =
+ app.querySelector("#displayNameBox")!;
+ let displayNameButton =
+ app.querySelector("#displayNameButton")!;
+ let loginNameWrapper =
+ app.querySelector("#loginNameWrapper")!;
+ let loginNameBox =
+ app.querySelector("#loginNameBox")!;
+ let passwordWrapper =
+ app.querySelector("#passwordWrapper")!;
+ let passwordBox =
+ app.querySelector("#passwordBox")!;
+ let passwordButton =
+ app.querySelector("#passwordButton")!;
- displayNameButton.onclick = async () => {
- let req = await client.changeDisplayName({
- changeDisplayNameRequest: { name: displayNameBox.value },
- });
- if (req.kind === "success") {
- showSuccess("Successfully changed display name");
- handleRoute();
- } else {
- showError(`Failed to update: ${req.msg}`);
- }
- };
+ let providerWrapper =
+ app.querySelector("#providerWrapper")!;
+ let providerNameBox =
+ app.querySelector("#providerNameBox")!;
+ let providerUserBox =
+ app.querySelector("#providerUserBox")!;
- // Initialize UI state
- refreshTotpUI();
- },
+ let accountTypeBox =
+ app.querySelector("#accountType")!;
+ displayNameBox.value = user.name;
+
+ guestBox.hidden = !user.guest;
+
+ // ---- DOM Elements ----
+ const totpStatusText = app.querySelector("#totpStatusText")!;
+ const enableBtn =
+ app.querySelector("#enableTotp")!;
+ const disableBtn =
+ app.querySelector("#disableTotp")!;
+ const showSecretBtn =
+ app.querySelector("#showSecret")!;
+ const secretBox = app.querySelector("#totpSecretBox")!;
+ const secretText =
+ app.querySelector("#totpSecretText")!;
+ const secretCanvas =
+ app.querySelector("#totpSecretCanvas")!;
+ let totpWrapper =
+ app.querySelector("#totpWrapper")!;
+
+ if (user.guest) {
+ for (let c of passwordButton.classList.values()) {
+ if (c.startsWith("bg-") || c.startsWith("hover:bg-"))
+ passwordButton.classList.remove(c);
+ }
+ }
+ if (user.guest) {
+ removeBgColor(
+ passwordButton,
+ displayNameButton,
+ enableBtn,
+ disableBtn,
+ showSecretBtn,
+ );
+
+ passwordButton.classList.add(
+ "bg-gray-700",
+ "hover:bg-gray-700",
+ );
+
+ passwordBox.disabled = true;
+ passwordBox.classList.add("color-white");
+
+ displayNameButton.disabled = true;
+ displayNameButton.classList.add("bg-gray-700", "color-white");
+
+ displayNameBox.disabled = true;
+ displayNameBox.classList.add("color-white");
+ enableBtn.classList.add("bg-gray-700", "hover:bg-gray-700");
+ disableBtn.classList.add("bg-gray-700", "hover:bg-gray-700");
+ showSecretBtn.classList.add("bg-gray-700", "hover:bg-gray-700");
+
+ enableBtn.disabled = true;
+ disableBtn.disabled = true;
+ showSecretBtn.disabled = true;
+
+ accountTypeBox.innerText = "Guest";
+ } else if (!isNullish(user.selfInfo?.loginName)) {
+ loginNameWrapper.hidden = false;
+ loginNameBox.innerText = user.selfInfo.loginName;
+ totpWrapper.hidden =false;
+ passwordWrapper.hidden = false;
+
+ accountTypeBox.innerText = "Normal";
+ } else if (
+ !isNullish(user.selfInfo?.providerId) &&
+ !isNullish(user.selfInfo?.providerUser)
+ ) {
+ providerWrapper.hidden = false;
+ providerNameBox.innerText = user.selfInfo.providerId;
+ providerUserBox.innerText = user.selfInfo.providerUser;
+
+ enableBtn.classList.add("bg-gray-700", "hover:bg-gray-700");
+ disableBtn.classList.add("bg-gray-700", "hover:bg-gray-700");
+ showSecretBtn.classList.add("bg-gray-700", "hover:bg-gray-700");
+
+ enableBtn.disabled = true;
+ disableBtn.disabled = true;
+ showSecretBtn.disabled = true;
+
+ removeBgColor(enableBtn, disableBtn, showSecretBtn);
+ passwordWrapper.hidden = true;
+ totpWrapper.hidden = true;
+
+ accountTypeBox.innerText = "Provider";
+ }
+
+ // ---- Update UI ----
+ function refreshTotpUI() {
+ if (totpEnabled) {
+ totpStatusText.textContent = "Status: Enabled";
+
+ enableBtn.classList.add("hidden");
+ disableBtn.classList.remove("hidden");
+ showSecretBtn.classList.remove("hidden");
+ } else {
+ totpStatusText.textContent = "Status: Disabled";
+
+ enableBtn.classList.remove("hidden");
+ disableBtn.classList.add("hidden");
+ showSecretBtn.classList.add("hidden");
+ secretBox.classList.add("hidden");
+ }
+ }
+
+ // ---- Button Events ----
+ enableBtn.onclick = async () => {
+ let res = await client.enableOtp();
+ if (res.kind === "success") {
+ navigateTo(url);
+ } else {
+ showError(`failed to activate OTP: ${res.msg}`);
+ }
};
- }
- addRoute("/profile", route);
+ disableBtn.onclick = async () => {
+ let res = await client.disableOtp();
+ if (res.kind === "success") {
+ navigateTo(url);
+ } else {
+ showError(`failed to deactivate OTP: ${res.msg}`);
+ }
+ };
+
+ showSecretBtn.onclick = () => {
+ if (!isNullish(totpSecret)) {
+ secretText.textContent = totpSecret;
+ renderOAuth2QRCode(secretCanvas, totpSecret);
+ }
+ secretBox.classList.toggle("hidden");
+ };
+
+ displayNameButton.onclick = async () => {
+ let req = await client.changeDisplayName({
+ changeDisplayNameRequest: {
+ name: displayNameBox.value,
+ },
+ });
+ if (req.kind === "success") {
+ showSuccess("Successfully changed display name");
+ handleRoute();
+ } else {
+ showError(`Failed to update: ${req.msg}`);
+ }
+ };
+
+ // Initialize UI state
+ refreshTotpUI();
+ },
+ };
+}
+
+addRoute("/profile", route);