From 7dde324689f216e4bdde0433edc39ef5fb00bda0 Mon Sep 17 00:00:00 2001
From: Kieran Klukas
Date: Mon, 27 Jul 2026 04:10:09 -0400
Subject: [PATCH] bug: fix biome issues
---
src/client/admin-clients.ts | 37 ++++++++-------
src/client/admin-invites.ts | 28 ++++++++----
src/html/admin-clients.html | 29 +++---------
src/html/admin-invites.html | 6 +--
src/index.ts | 24 ++++------
src/lib/oauth/pages.ts | 90 +++++++++++++++++--------------------
src/routes/api.ts | 12 ++---
src/routes/auth.ts | 2 +-
src/routes/oauth/device.ts | 6 ++-
src/routes/oauth/profile.ts | 54 ++++++++++------------
src/routes/oauth/token.ts | 5 +++
src/routes/passkeys.ts | 8 ++--
12 files changed, 144 insertions(+), 157 deletions(-)
diff --git a/src/client/admin-clients.ts b/src/client/admin-clients.ts
index e947938..5921af3 100644
--- a/src/client/admin-clients.ts
+++ b/src/client/admin-clients.ts
@@ -1,3 +1,18 @@
+declare global {
+ interface Window {
+ toggleClient: (clientId: string) => Promise;
+ setUserRole: (
+ clientId: string,
+ username: string,
+ role: string,
+ ) => Promise;
+ editClient: (clientId: string) => Promise;
+ deleteClient: (clientId: string, event?: Event) => Promise;
+ removeRedirectUri: (btn: HTMLButtonElement) => void;
+ regenerateSecret: (clientId: string, event?: Event) => Promise;
+ revokeUserPermission: (clientId: string, username: string) => Promise;
+ }
+}
const token = localStorage.getItem("indiko_session");
import "./ds";
@@ -80,14 +95,6 @@ interface ClientUser {
lastUsed: number;
}
-interface AppPermission {
- username: string;
- name: string;
- scopes: string[];
- grantedAt: number;
- lastUsed: number;
-}
-
async function loadClients() {
try {
const response = await fetch("/api/admin/clients", {
@@ -167,7 +174,7 @@ function displayClients(clients: Client[]) {
.join("");
}
-(window as any).toggleClient = async (clientId: string) => {
+window.toggleClient = async (clientId: string) => {
const card = document.querySelector(
`[data-client-id="${clientId}"]`,
) as HTMLElement;
@@ -295,7 +302,7 @@ function displayClients(clients: Client[]) {
}
};
-(window as any).setUserRole = async (
+window.setUserRole = async (
clientId: string,
username: string,
role: string,
@@ -324,7 +331,7 @@ function displayClients(clients: Client[]) {
}
};
-(window as any).editClient = async (clientId: string) => {
+window.editClient = async (clientId: string) => {
try {
const response = await fetch(
`/api/admin/clients/${encodeURIComponent(clientId)}`,
@@ -374,7 +381,7 @@ function displayClients(clients: Client[]) {
}
};
-(window as any).deleteClient = async (clientId: string, event?: Event) => {
+window.deleteClient = async (clientId: string, event?: Event) => {
const btn = event?.target as HTMLButtonElement | undefined;
// Double-click confirmation pattern
@@ -455,7 +462,7 @@ addRedirectUriBtn.addEventListener("click", () => {
redirectUrisList.appendChild(newItem);
});
-(window as any).removeRedirectUri = (btn: HTMLButtonElement) => {
+window.removeRedirectUri = (btn: HTMLButtonElement) => {
const items = redirectUrisList.querySelectorAll(".redirect-uri-item");
if (items.length > 1) {
btn.parentElement?.remove();
@@ -576,7 +583,7 @@ clientForm.addEventListener("submit", async (e) => {
}
});
-(window as any).regenerateSecret = async (clientId: string, event?: Event) => {
+window.regenerateSecret = async (clientId: string, event?: Event) => {
const btn = event?.target as HTMLButtonElement | undefined;
// Double-click confirmation pattern (same as delete)
@@ -647,7 +654,7 @@ clientForm.addEventListener("submit", async (e) => {
}
};
-(window as any).revokeUserPermission = async (
+window.revokeUserPermission = async (
clientId: string,
username: string,
event?: Event,
diff --git a/src/client/admin-invites.ts b/src/client/admin-invites.ts
index 298d753..01077ea 100644
--- a/src/client/admin-invites.ts
+++ b/src/client/admin-invites.ts
@@ -1,5 +1,15 @@
import "./ds";
+declare global {
+ interface Window {
+ submitCreateInvite: () => Promise;
+ closeCreateInviteModal: () => void;
+ editInvite: (inviteId: number) => Promise;
+ submitEditInvite: () => Promise;
+ closeEditInviteModal: () => void;
+ deleteInvite: (inviteId: number, event?: Event) => Promise;
+ }
+}
const token = localStorage.getItem("indiko_session");
const invitesList = document.getElementById("invitesList") as HTMLElement;
const createInviteBtn = document.getElementById(
@@ -244,8 +254,8 @@ function closeCreateInviteModal() {
}
// Expose functions to global scope for HTML onclick handlers
-(window as any).submitCreateInvite = submitCreateInvite;
-(window as any).closeCreateInviteModal = closeCreateInviteModal;
+window.submitCreateInvite = submitCreateInvite;
+window.closeCreateInviteModal = closeCreateInviteModal;
async function loadInvites() {
try {
@@ -386,7 +396,7 @@ createInviteBtn.addEventListener("click", createInvite);
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
closeCreateInviteModal();
- closeEditInviteModal();
+ window.closeEditInviteModal();
}
});
@@ -399,14 +409,14 @@ document.getElementById("createInviteModal")?.addEventListener("click", (e) => {
document.getElementById("editInviteModal")?.addEventListener("click", (e) => {
if (e.target === e.currentTarget) {
- closeEditInviteModal();
+ window.closeEditInviteModal();
}
});
let currentEditInviteId: number | null = null;
// Make editInvite globally available for onclick handler
-(window as any).editInvite = async (inviteId: number) => {
+window.editInvite = async (inviteId: number) => {
try {
const response = await fetch("/api/invites", {
headers: {
@@ -466,7 +476,7 @@ let currentEditInviteId: number | null = null;
}
};
-(window as any).submitEditInvite = async () => {
+window.submitEditInvite = async () => {
if (currentEditInviteId === null) return;
const maxUsesInput = document.getElementById(
@@ -510,7 +520,7 @@ let currentEditInviteId: number | null = null;
}
await loadInvites();
- closeEditInviteModal();
+ window.closeEditInviteModal();
} catch (error) {
console.error("Failed to update invite:", error);
alert("Failed to update invite");
@@ -520,7 +530,7 @@ let currentEditInviteId: number | null = null;
}
};
-(window as any).closeEditInviteModal = () => {
+window.closeEditInviteModal = () => {
const modal = document.getElementById("editInviteModal");
if (modal) {
modal.style.display = "none";
@@ -535,7 +545,7 @@ let currentEditInviteId: number | null = null;
}
};
-(window as any).deleteInvite = async (inviteId: number, event?: Event) => {
+window.deleteInvite = async (inviteId: number, event?: Event) => {
const btn = event?.target as HTMLButtonElement | undefined;
// Double-click confirmation pattern
diff --git a/src/html/admin-clients.html b/src/html/admin-clients.html
index b5aa1b1..8500c6c 100644
--- a/src/html/admin-clients.html
+++ b/src/html/admin-clients.html
@@ -126,23 +126,6 @@
letter-spacing: 0.05rem;
}
- footer a {
- color: var(--berry-crush);
- text-decoration: none;
- transition: color 0.2s;
- }
-
- footer a:hover {
- color: var(--rosewood);
- text-decoration: underline;
- }
-
- .back-link {
- margin-top: 0.5rem;
- font-size: 0.875rem;
- color: var(--old-rose);
- }
-
.actions {
display: flex;
justify-content: space-between;
@@ -643,8 +626,8 @@
-
+
@@ -682,14 +665,14 @@
⚠️ Save these credentials now. You won't be able to see the secret again!
-
+
Client ID
-
+
Client Secret
diff --git a/src/html/admin-invites.html b/src/html/admin-invites.html
index fd64933..b97ff87 100644
--- a/src/html/admin-invites.html
+++ b/src/html/admin-invites.html
@@ -324,13 +324,13 @@
Public message that will be shown to users when they use this invite
-
+
diff --git a/src/index.ts b/src/index.ts
index 796a113..6469c1b 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -221,35 +221,27 @@ Policy: https://tangled.org/dunkirk.sh/indiko/blob/main/SECURITY.md
if (req.method === "DELETE") return deleteInvite(req);
return new Response("Method not allowed", { status: 405 });
},
- "/api/admin/users/:id/disable": (req: Request) => {
+ "/api/admin/users/:id/disable": (req) => {
if (req.method === "POST") {
- const url = new URL(req.url);
- const userId = url.pathname.split("/")[4];
- return disableUser(req, userId);
+ return disableUser(req, req.params.id);
}
return new Response("Method not allowed", { status: 405 });
},
- "/api/admin/users/:id/enable": (req: Request) => {
+ "/api/admin/users/:id/enable": (req) => {
if (req.method === "POST") {
- const url = new URL(req.url);
- const userId = url.pathname.split("/")[4];
- return enableUser(req, userId);
+ return enableUser(req, req.params.id);
}
return new Response("Method not allowed", { status: 405 });
},
- "/api/admin/users/:id/tier": (req: Request) => {
+ "/api/admin/users/:id/tier": (req) => {
if (req.method === "PUT") {
- const url = new URL(req.url);
- const userId = url.pathname.split("/")[4];
- return updateUserTier(req, userId);
+ return updateUserTier(req, req.params.id);
}
return new Response("Method not allowed", { status: 405 });
},
- "/api/admin/users/:id/delete": (req: Request) => {
+ "/api/admin/users/:id/delete": (req) => {
if (req.method === "DELETE") {
- const url = new URL(req.url);
- const userId = url.pathname.split("/")[4];
- return deleteUser(req, userId);
+ return deleteUser(req, req.params.id);
}
return new Response("Method not allowed", { status: 405 });
},
diff --git a/src/lib/oauth/pages.ts b/src/lib/oauth/pages.ts
index 7eb1dd9..fa41f2b 100644
--- a/src/lib/oauth/pages.ts
+++ b/src/lib/oauth/pages.ts
@@ -10,18 +10,11 @@ export function escapeHtml(value: string): string {
}
export const BASE_STYLES = `
- :root {
- --mahogany: #26242b;
- --lavender: #d9d0de;
- --old-rose: #bc8da0;
- --rosewood: #a04668;
- --berry-crush: #ab4967;
- }
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
- font-family: "Space Grotesk", sans-serif;
- background: var(--mahogany);
- color: var(--lavender);
+ font-family: var(--font);
+ background: var(--ink);
+ color: var(--paper);
min-height: 100vh;
display: flex;
align-items: center;
@@ -31,22 +24,19 @@ export const BASE_STYLES = `
h1 {
font-size: 2rem;
font-weight: 700;
- background: linear-gradient(135deg, var(--old-rose), var(--rosewood));
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- background-clip: text;
+ color: var(--accent);
margin-bottom: 1.5rem;
letter-spacing: -0.05rem;
}
p {
line-height: 1.8;
margin-bottom: 1rem;
- color: var(--lavender);
+ color: var(--paper);
}
code {
background: rgba(12, 23, 19, 0.8);
padding: 0.25rem 0.5rem;
- color: var(--berry-crush);
+ color: var(--accent);
font-size: 0.875rem;
word-break: break-all;
display: inline-block;
@@ -55,37 +45,37 @@ export const BASE_STYLES = `
.box {
max-width: 600px;
background: rgba(188, 141, 160, 0.05);
- border: 2px solid var(--rosewood);
+ border: 2px solid var(--accent-deep);
padding: 2.5rem;
}
.error-details {
background: rgba(160, 70, 104, 0.1);
- border-left: 4px solid var(--rosewood);
+ border-left: 4px solid var(--accent-deep);
padding: 1rem;
margin: 1.5rem 0;
}
.error-details strong {
display: block;
margin-bottom: 0.5rem;
- color: var(--old-rose);
+ color: var(--paper-dim);
}
.hint {
margin-top: 1.5rem;
font-size: 0.875rem;
- color: var(--old-rose);
+ color: var(--paper-dim);
}
button {
position: relative;
padding: 1rem 1.5rem;
- border: 4px solid var(--mahogany);
- font-family: "Space Grotesk", sans-serif;
+ border: 4px solid var(--ink-sunken);
+ font-family: var(--font);
font-size: 1rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1rem;
cursor: pointer;
transition: all 0.15s ease;
- box-shadow: 6px 6px 0 var(--mahogany);
+ box-shadow: var(--shadow-hard);
}
button::before {
content: '';
@@ -98,14 +88,14 @@ export const BASE_STYLES = `
}
button:hover {
transform: translate(3px, 3px);
- box-shadow: 3px 3px 0 var(--mahogany);
+ box-shadow: var(--shadow-hard-hover);
}
button:hover::before {
top: -7px; left: -7px; right: -7px; bottom: -7px;
}
button:active {
transform: translate(6px, 6px);
- box-shadow: 0 0 0 var(--mahogany);
+ box-shadow: 0 0 0 var(--ink-sunken);
}
`;
@@ -126,6 +116,7 @@ function page(title: string, styles: string, body: string): Response {
+
@@ -177,7 +168,7 @@ const CONSENT_STYLES = `
max-width: 30rem;
width: 100%;
background: rgba(188, 141, 160, 0.05);
- border: 1px solid var(--old-rose);
+ border: 1px solid var(--paper-dim);
padding: 2.5rem;
}
.app-header {
@@ -207,24 +198,24 @@ const CONSENT_STYLES = `
.app-name {
font-size: 1.375rem;
font-weight: 700;
- color: var(--lavender);
+ color: var(--paper);
line-height: 1.2;
}
.app-url {
font-size: 0.875rem;
- color: var(--old-rose);
+ color: var(--paper-dim);
font-family: monospace;
margin-top: 0.25rem;
}
.app-description {
font-size: 0.9375rem;
- color: var(--old-rose);
+ color: var(--paper-dim);
line-height: 1.6;
margin-top: 0.5rem;
}
.request-text {
font-size: 1rem;
- color: var(--old-rose);
+ color: var(--paper-dim);
margin-bottom: 1.5rem;
line-height: 1.6;
}
@@ -232,11 +223,11 @@ const CONSENT_STYLES = `
margin-bottom: 1.75rem;
padding: 1.25rem;
background: rgba(12, 23, 19, 0.4);
- border: 1px solid var(--old-rose);
+ border: 1px solid var(--paper-dim);
}
.scope-title {
font-size: 0.75rem;
- color: var(--old-rose);
+ color: var(--paper-dim);
text-transform: uppercase;
letter-spacing: 0.1rem;
margin-bottom: 0.75rem;
@@ -248,7 +239,7 @@ const CONSENT_STYLES = `
gap: 0.25rem;
}
.scope-list li {
- color: var(--lavender);
+ color: var(--paper);
font-size: 0.9375rem;
line-height: 1.5;
}
@@ -263,13 +254,13 @@ const CONSENT_STYLES = `
}
.scope-list label:hover {
background: rgba(188, 141, 160, 0.1);
- border-color: var(--old-rose);
+ border-color: var(--paper-dim);
}
.scope-list input[type="checkbox"] {
appearance: none;
width: 1.25rem;
height: 1.25rem;
- border: 2px solid var(--old-rose);
+ border: 2px solid var(--paper-dim);
background: rgba(12, 23, 19, 0.6);
cursor: pointer;
flex-shrink: 0;
@@ -278,8 +269,8 @@ const CONSENT_STYLES = `
margin: 0;
}
.scope-list input[type="checkbox"]:checked {
- background: var(--berry-crush);
- border-color: var(--berry-crush);
+ background: var(--accent);
+ border-color: var(--accent);
}
.scope-list input[type="checkbox"]:checked::after {
content: "✓";
@@ -287,14 +278,14 @@ const CONSENT_STYLES = `
inset: 0;
display: grid;
place-items: center;
- color: var(--lavender);
+ color: var(--paper);
font-size: 0.875rem;
font-weight: 700;
}
.scope-list input[type="checkbox"]:disabled { cursor: not-allowed; opacity: 0.7; }
.req {
font-style: normal;
- color: var(--old-rose);
+ color: var(--paper-dim);
font-size: 0.75rem;
margin-left: 0.5rem;
}
@@ -305,33 +296,33 @@ const CONSENT_STYLES = `
}
.buttons button { flex: 1; }
.allow {
- background: var(--berry-crush);
- color: var(--lavender);
+ background: var(--accent);
+ color: var(--paper);
}
- .allow::before { border-color: var(--rosewood); }
+ .allow::before { border-color: var(--accent-deep); }
.deny {
background: transparent;
- color: var(--old-rose);
+ color: var(--paper-dim);
}
- .deny::before { border-color: var(--old-rose); }
+ .deny::before { border-color: var(--paper-dim); }
.who {
margin-top: 1.5rem;
text-align: center;
font-size: 0.8125rem;
- color: var(--old-rose);
+ color: var(--paper-dim);
}
- .who strong { color: var(--lavender); font-weight: 600; }
+ .who strong { color: var(--paper); font-weight: 600; }
.me-identity {
margin-top: 1rem;
padding: 0.75rem 1rem;
background: rgba(12, 23, 19, 0.4);
- border: 1px solid var(--old-rose);
+ border: 1px solid var(--paper-dim);
font-size: 0.8125rem;
- color: var(--old-rose);
+ color: var(--paper-dim);
text-align: center;
}
.me-identity code {
- color: var(--berry-crush);
+ color: var(--accent);
font-size: 0.8125rem;
}
`;
@@ -426,6 +417,7 @@ export function consentPage(opts: ConsentPageOptions): Response {
+
diff --git a/src/routes/api.ts b/src/routes/api.ts
index 73fb096..1647767 100644
--- a/src/routes/api.ts
+++ b/src/routes/api.ts
@@ -144,10 +144,12 @@ export async function updateProfile(req: Request): Promise {
const origin = process.env.ORIGIN || "http://localhost:3000";
const indikoProfileUrl = `${origin}/u/${user.username}`;
- const verification = await verifyDomain(
- validation.canonicalUrl!,
- indikoProfileUrl,
- );
+ const canonicalUrl = validation.canonicalUrl;
+ if (!canonicalUrl) {
+ return Response.json({ error: "Invalid URL format" }, { status: 400 });
+ }
+
+ const verification = await verifyDomain(canonicalUrl, indikoProfileUrl);
if (!verification.success) {
return Response.json(
{ error: verification.error || "Failed to verify domain" },
@@ -410,7 +412,7 @@ export function disableUser(req: Request, userId: string): Response {
}
// Prevent disabling self
- if (targetUserId === user.id) {
+ if (targetUserId === user.userId) {
return Response.json(
{ error: "Cannot disable your own account" },
{ status: 400 },
diff --git a/src/routes/auth.ts b/src/routes/auth.ts
index 8256794..0133acd 100644
--- a/src/routes/auth.ts
+++ b/src/routes/auth.ts
@@ -12,7 +12,7 @@ import {
} from "@simplewebauthn/server";
import { authenticate } from "ldap-authentication";
import { db } from "../db";
-import { checkLdapGroupMembership } from "../ldap-cleanup";
+import { checkLdapGroupMembership, checkLdapUser } from "../ldap-cleanup";
const RP_NAME = "Indiko";
diff --git a/src/routes/oauth/device.ts b/src/routes/oauth/device.ts
index c79f5b3..2a6aa07 100644
--- a/src/routes/oauth/device.ts
+++ b/src/routes/oauth/device.ts
@@ -19,9 +19,11 @@ const USER_CODE_CHARS = "BCDFGHJKLMNPQRSTVWXZ";
function generateUserCode(): string {
const bytes = crypto.randomBytes(8);
const chars: string[] = [];
- for (let i = 0; i < 8; i++) {
+ let i = 0;
+ for (const byte of bytes) {
if (i === 4) chars.push("-");
- chars.push(USER_CODE_CHARS[bytes[i] % USER_CODE_CHARS.length]);
+ chars.push(USER_CODE_CHARS[byte % USER_CODE_CHARS.length] as string);
+ i++;
}
return chars.join("");
}
diff --git a/src/routes/oauth/profile.ts b/src/routes/oauth/profile.ts
index 816c5a1..2658158 100644
--- a/src/routes/oauth/profile.ts
+++ b/src/routes/oauth/profile.ts
@@ -66,23 +66,17 @@ export function userProfile(req: Request): Response {
+