Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ RUN apk add --no-cache python3 make g++ git
COPY package.json package-lock.json ./
COPY packages ./packages
COPY changelog ./changelog
COPY scripts ./scripts
RUN npm ci
RUN npm run build
RUN npm prune --omit=dev
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"dev:ui": "npm run dev -w @DarkAuth/user-ui",
"dev:admin": "npm run dev -w @DarkAuth/admin-ui",
"dev:brochureware": "npm run dev -w @DarkAuth/brochureware",
"build": "npm run build --workspaces --if-present",
"build": "node scripts/build-workspaces.mjs",
"build:ui": "npm run build -w @DarkAuth/user-ui",
"build:admin": "npm run build -w @DarkAuth/admin-ui",
"build:api": "npm run build -w @DarkAuth/api",
Expand Down
7 changes: 4 additions & 3 deletions packages/admin-ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import AdminLogin from "@/components/Login";
import { Toaster } from "@/components/ui/toaster";
import adminApiService from "@/services/api";
import authService from "@/services/auth";
import { logger } from "@/services/logger";
import AdminOtp from "./pages/AdminOtp";
import AdminUserCreate from "./pages/AdminUserCreate";
import AdminUserEdit from "./pages/AdminUserEdit";
Expand Down Expand Up @@ -74,7 +75,7 @@ const App = () => {
try {
await adminApiService.getAdminSession();
} catch (error) {
console.error("Session refresh failed:", error);
logger.error(error, "Session refresh failed");
setAdminSession(null);
authService.clearSession();
}
Expand Down Expand Up @@ -112,7 +113,7 @@ const App = () => {
try {
await adminApiService.getAdminSession();
} catch (error) {
console.error("Session refresh failed:", error);
logger.error(error, "Session refresh failed");
setAdminSession(null);
authService.clearSession();
}
Expand All @@ -126,7 +127,7 @@ const App = () => {
authService.clearSession();
window.location.assign("/");
} catch (error) {
console.error("Logout failed:", error);
logger.error(error, "Logout failed");
}
};

Expand Down
18 changes: 10 additions & 8 deletions packages/admin-ui/src/components/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import AuthFrame from "@/components/auth/AuthFrame";
import { Button } from "@/components/ui/button";
import adminApiService from "@/services/api";
import authService from "@/services/auth";
import { logger } from "@/services/logger";
import adminOpaqueService, { type AdminOpaqueLoginState } from "@/services/opaque";
import styles from "./Login.module.css";

Expand Down Expand Up @@ -92,33 +93,34 @@ export default function AdminLogin({ onLogin }: AdminLoginProps) {
setErrors({});

try {
console.debug("[admin-ui] login: start", { email: formData.email });
logger.debug({ email: formData.email }, "[admin-ui] login start");
// Start OPAQUE admin login
const loginStart = await adminOpaqueService.startLogin(formData.email, formData.password);
setOpaqueState(loginStart.state);

console.debug("[admin-ui] login: sending /opaque/login/start", {
reqLen: loginStart.request.length,
});
logger.debug({ requestLength: loginStart.request.length }, "[admin-ui] login start request");
const loginStartResponse = await adminApiService.adminOpaqueLoginStart({
email: formData.email,
request: loginStart.request,
});
console.debug("[admin-ui] login: start response", loginStartResponse);
logger.debug(loginStartResponse, "[admin-ui] login start response");

// Finish OPAQUE login
const loginFinish = await adminOpaqueService.finishLogin(
loginStartResponse.message,
loginStart.state
);
console.debug("[admin-ui] login: finish ke3 len", { len: loginFinish.request.length });
logger.debug(
{ requestLength: loginFinish.request.length },
"[admin-ui] login finish payload"
);

// Send login finish request to server
const loginFinishResponse = await adminApiService.adminOpaqueLoginFinish({
finish: loginFinish.request,
sessionId: loginStartResponse.sessionId,
});
console.debug("[admin-ui] login: finish response", loginFinishResponse);
logger.debug(loginFinishResponse, "[admin-ui] login finish response");

const name = loginFinishResponse.admin.name;
const email = loginFinishResponse.admin.email;
Expand Down Expand Up @@ -161,7 +163,7 @@ export default function AdminLogin({ onLogin }: AdminLoginProps) {
});
} catch {}
} catch (error) {
console.error("Admin login failed:", error);
logger.error(error, "Admin login failed");

// Clear sensitive data on error
if (opaqueState) {
Expand Down
3 changes: 2 additions & 1 deletion packages/admin-ui/src/pages/AdminUsers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
import { useToast } from "@/hooks/use-toast";
import adminApiService, { type AdminUser } from "@/services/api";
import { sha256Base64Url } from "@/services/hash";
import { logger } from "@/services/logger";
import adminOpaqueService from "@/services/opaque-cloudflare";

export default function AdminUsers() {
Expand All @@ -43,7 +44,7 @@ export default function AdminUsers() {
setTotalPages(response.pagination.totalPages);
setTotalCount(response.pagination.total);
} catch (error) {
console.error("Failed to load admin users:", error);
logger.error(error, "Failed to load admin users");
setError(error instanceof Error ? error.message : "Failed to load admin users");
toast({
title: "Error",
Expand Down
5 changes: 3 additions & 2 deletions packages/admin-ui/src/pages/AuditLogs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import adminApiService, { type AuditLog, type AuditLogFilters } from "@/services/api";
import { logger } from "@/services/logger";

const EVENT_TYPES = [
"user_login",
Expand Down Expand Up @@ -95,7 +96,7 @@ export default function AuditLogs() {
setTotalPages(response.pagination.totalPages);
setTotalLogs(response.pagination.total);
} catch (error) {
console.error("Failed to load audit logs:", error);
logger.error(error, "Failed to load audit logs");
setError(error instanceof Error ? error.message : "Failed to load audit logs");
} finally {
setLoading(false);
Expand Down Expand Up @@ -149,7 +150,7 @@ export default function AuditLogs() {
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
} catch (error) {
console.error("Failed to export audit logs:", error);
logger.error(error, "Failed to export audit logs");
setError(error instanceof Error ? error.message : "Failed to export audit logs");
}
};
Expand Down
12 changes: 2 additions & 10 deletions packages/admin-ui/src/pages/Branding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { useToast } from "@/hooks/use-toast";
import adminApiService, { type AdminSetting } from "@/services/api";
import { logger } from "@/services/logger";

declare global {
interface Window {
Expand Down Expand Up @@ -147,9 +148,6 @@ export default function Branding() {
// Only keep the primary color from the loaded data
const loadedColors = (col?.value as Record<string, string>) || {};
const loadedColorsDark = (colDark?.value as Record<string, string>) || {};
console.log("LOADED COLORS FROM BACKEND:", loadedColors);
console.log("LOADED COLORS DARK FROM BACKEND:", loadedColorsDark);

// Only extract the primary field
const lightPrimary =
typeof loadedColors === "object" && loadedColors.primary ? loadedColors.primary : "#6600cc";
Expand All @@ -158,9 +156,6 @@ export default function Branding() {
? loadedColorsDark.primary
: "#aec1e0";

console.log("SETTING LIGHT PRIMARY:", lightPrimary);
console.log("SETTING DARK PRIMARY:", darkPrimary);

setColors({ primary: lightPrimary });
setColorsDark({ primary: darkPrimary });
setCustomCss((css?.value as string) || "");
Expand Down Expand Up @@ -241,9 +236,6 @@ export default function Branding() {
setSaving(true);
const identityToSave = { title: titleInput, tagline: taglineInput };

console.log("SAVING COLORS:", colors);
console.log("SAVING COLORS DARK:", colorsDark);

await adminApiService.updateSetting("branding.identity", identityToSave);
if (colors) await adminApiService.updateSetting("branding.colors", colors);
if (wording) await adminApiService.updateSetting("branding.wording", wording);
Expand All @@ -254,7 +246,7 @@ export default function Branding() {
if (favicon) await adminApiService.updateSetting("branding.favicon", favicon);
if (faviconDark) await adminApiService.updateSetting("branding.favicon_dark", faviconDark);
if (colorsDark && Object.keys(colorsDark).length > 0) {
console.log("ACTUALLY SAVING COLORS DARK:", colorsDark);
logger.info({ colorsDark }, "Saving dark theme colors");
await adminApiService.updateSetting("branding.colors_dark", colorsDark);
}
toast({ title: "Branding saved" });
Expand Down
3 changes: 2 additions & 1 deletion packages/admin-ui/src/pages/GroupCreate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import adminApiService, { type Permission } from "@/services/api";
import { logger } from "@/services/logger";

export default function GroupCreate() {
const navigate = useNavigate();
Expand All @@ -29,7 +30,7 @@ export default function GroupCreate() {
const permissionsData = await adminApiService.getPermissions();
setPermissions(permissionsData);
} catch (error) {
console.error("Failed to load permissions:", error);
logger.error(error, "Failed to load permissions");
setError(error instanceof Error ? error.message : "Failed to load permissions");
} finally {
setLoadingPermissions(false);
Expand Down
3 changes: 2 additions & 1 deletion packages/admin-ui/src/pages/Groups.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
PaginationPrevious,
} from "@/components/ui/pagination";
import adminApiService, { type Group } from "@/services/api";
import { logger } from "@/services/logger";

export default function Groups() {
const navigate = useNavigate();
Expand All @@ -39,7 +40,7 @@ export default function Groups() {
setTotalPages(response.pagination.totalPages);
setTotalCount(response.pagination.total);
} catch (error) {
console.error("Failed to load groups:", error);
logger.error(error, "Failed to load groups");
setError(error instanceof Error ? error.message : "Failed to load groups");
} finally {
setLoading(false);
Expand Down
3 changes: 2 additions & 1 deletion packages/admin-ui/src/pages/NotFound.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
import PageHeader from "@/components/layout/page-header";
import { logger } from "@/services/logger";

const NotFound = () => {
const location = useLocation();

useEffect(() => {
console.error("404 Error: User attempted to access non-existent route:", location.pathname);
logger.error({ path: location.pathname }, "404 route access");
}, [location.pathname]);

return (
Expand Down
5 changes: 3 additions & 2 deletions packages/admin-ui/src/pages/Users.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
import UserCell from "@/components/user/user-cell";
import adminApiService, { type Group, type User } from "@/services/api";
import { sha256Base64Url } from "@/services/hash";
import { logger } from "@/services/logger";
import adminOpaqueService from "@/services/opaque-cloudflare";

interface UserWithDetails extends User {
Expand Down Expand Up @@ -72,7 +73,7 @@ export default function Users() {
setTotalPages(response.pagination.totalPages);
setTotalCount(response.pagination.total);
} catch (error) {
console.error("Failed to load users:", error);
logger.error(error, "Failed to load users");
setError(error instanceof Error ? error.message : "Failed to load users");
} finally {
setLoading(false);
Expand All @@ -84,7 +85,7 @@ export default function Users() {
const [groupsData] = await Promise.all([adminApiService.getGroups()]);
setGroups(groupsData);
} catch (error) {
console.error("Failed to load groups:", error);
logger.error(error, "Failed to load groups");
}
}, []);

Expand Down
8 changes: 5 additions & 3 deletions packages/admin-ui/src/services/auth.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { logger } from "./logger";

interface StoredSession {
adminId: string;
name?: string;
Expand All @@ -17,7 +19,7 @@ interface StoredLoginInfo {

const SESSION_STORAGE_KEY = "DarkAuth_admin_session";
const LOGIN_INFO_KEY = "DarkAuth_admin_login_info";
const SESSION_REFRESH_INTERVAL = 10 * 60 * 1000; // Refresh every 10 minutes (session lasts 15 min)
const SESSION_REFRESH_INTERVAL = 10 * 60 * 1000;

class AuthService {
private refreshTimer: ReturnType<typeof setInterval> | null = null;
Expand Down Expand Up @@ -100,7 +102,7 @@ class AuthService {
try {
await onRefresh();
} catch (error) {
console.error("Session refresh failed:", error);
logger.error(error, "Session refresh failed");
// If refresh fails, clear the stored session
this.clearSession();
}
Expand All @@ -112,7 +114,7 @@ class AuthService {
try {
await onRefresh();
} catch (error) {
console.error("Session refresh on visibility change failed:", error);
logger.error(error, "Session refresh on visibility change failed");
}
}
};
Expand Down
53 changes: 53 additions & 0 deletions packages/admin-ui/src/services/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const levelMethod: Record<string, keyof Console> = {
error: "error",
warn: "warn",
info: "info",
debug: "debug",
};

type LogLevel = keyof typeof levelMethod;
type LogDetail = unknown;

function serialize(detail: LogDetail) {
if (detail == null) return undefined;
if (detail instanceof Error) {
return {
error: {
name: detail.name,
message: detail.message,
stack: detail.stack,
},
};
}
if (typeof detail === "string") return { detail };
if (Array.isArray(detail)) return { detail };
if (typeof detail === "object") return detail as Record<string, unknown>;
return { detail };
}

function emit(level: LogLevel, detail?: LogDetail, message?: string) {
const method = levelMethod[level] || "log";
const payload: Record<string, unknown> = {
level,
timestamp: new Date().toISOString(),
};
if (message) payload.message = message;
const extra = serialize(detail);
if (extra && typeof extra === "object") Object.assign(payload, extra);
(console[method] || console.log).call(console, JSON.stringify(payload));
}

export const logger = {
error(detail?: LogDetail, message?: string) {
emit("error", detail, message);
},
warn(detail?: LogDetail, message?: string) {
emit("warn", detail, message);
},
info(detail?: LogDetail, message?: string) {
emit("info", detail, message);
},
debug(detail?: LogDetail, message?: string) {
emit("debug", detail, message);
},
};
Loading
Loading