-
+
by Keplr
diff --git a/apps/oko_admin_web/src/app/audit_logs/layout.tsx b/apps/oko_admin_web/src/app/audit_logs/layout.tsx
new file mode 100644
index 000000000..f3618c0ef
--- /dev/null
+++ b/apps/oko_admin_web/src/app/audit_logs/layout.tsx
@@ -0,0 +1,17 @@
+import React from "react";
+
+import styles from "@oko-wallet-admin/styles/layout_with_side_bar.module.scss";
+import { SideBar } from "@oko-wallet-admin/components/side_bar/side_bar";
+
+export default function AuditLogsLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+
+ {children}
+
+ );
+}
diff --git a/apps/oko_admin_web/src/app/audit_logs/page.tsx b/apps/oko_admin_web/src/app/audit_logs/page.tsx
new file mode 100644
index 000000000..17f76157b
--- /dev/null
+++ b/apps/oko_admin_web/src/app/audit_logs/page.tsx
@@ -0,0 +1,5 @@
+import { AuditLogsView } from "@oko-wallet-admin/components/audit_logs/audit_logs_view";
+
+export default function AuditLogsPage() {
+ return
;
+}
diff --git a/apps/oko_admin_web/src/components/audit_logs/audit_logs_filter.module.scss b/apps/oko_admin_web/src/components/audit_logs/audit_logs_filter.module.scss
new file mode 100644
index 000000000..554594224
--- /dev/null
+++ b/apps/oko_admin_web/src/components/audit_logs/audit_logs_filter.module.scss
@@ -0,0 +1,57 @@
+.container {
+ background-color: var(--white);
+ border: 1px solid var(--gray-200);
+ border-radius: 0.5rem;
+ padding: 1.5rem;
+ margin-bottom: 1.5rem;
+}
+
+.title {
+ margin-bottom: 1rem;
+ color: var(--gray-900);
+}
+
+.filters {
+ display: grid;
+ grid-template-columns: repeat(5, 1fr);
+ gap: 1rem;
+ margin-bottom: 1.5rem;
+}
+
+.filterGroup {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.filterGroup:has(.select) {
+ gap: 14px;
+}
+
+.label {
+ font-size: 0.875rem;
+ font-weight: 500;
+ color: var(--gray-700);
+}
+
+.select {
+ height: 42px;
+ padding: 10px 14px;
+ border: 1px solid var(--border-primary);
+ border-radius: 8px;
+ background-color: var(--white);
+ font-size: 0.875rem;
+ box-sizing: border-box;
+
+ &:focus {
+ outline: none;
+ border-color: var(--blue-500);
+ box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
+ }
+}
+
+.actions {
+ display: flex;
+ gap: 0.75rem;
+ justify-content: flex-end;
+}
diff --git a/apps/oko_admin_web/src/components/audit_logs/audit_logs_filter.tsx b/apps/oko_admin_web/src/components/audit_logs/audit_logs_filter.tsx
new file mode 100644
index 000000000..a87136e76
--- /dev/null
+++ b/apps/oko_admin_web/src/components/audit_logs/audit_logs_filter.tsx
@@ -0,0 +1,153 @@
+"use client";
+
+import { useState, type FC } from "react";
+import type { AuditEventFilter } from "@oko-wallet/oko-types/admin";
+import { Input } from "@oko-wallet/oko-common-ui/input";
+import { Button } from "@oko-wallet/oko-common-ui/button";
+import { Typography } from "@oko-wallet/oko-common-ui/typography";
+
+import styles from "./audit_logs_filter.module.scss";
+
+const TARGET_TYPES = [
+ { value: "", label: "All Target Types" },
+ { value: "customer", label: "Customer" },
+ { value: "node", label: "Node" },
+ { value: "user", label: "User" },
+ { value: "policy", label: "Policy" },
+];
+
+const ACTIONS = [
+ { value: "", label: "All Actions" },
+ { value: "create", label: "Create" },
+ { value: "update", label: "Update" },
+ { value: "delete", label: "Delete" },
+ { value: "enable", label: "Enable" },
+ { value: "disable", label: "Disable" },
+ { value: "login", label: "Login" },
+ { value: "logout", label: "Logout" },
+ { value: "policy_update", label: "Policy Update" },
+];
+
+const OUTCOMES = [
+ { value: "", label: "All Outcomes" },
+ { value: "success", label: "Success" },
+ { value: "failure", label: "Failure" },
+ { value: "denied", label: "Denied" },
+];
+
+interface AuditLogsFilterProps {
+ filter: AuditEventFilter;
+ onFilterChange: (filter: AuditEventFilter) => void;
+}
+
+export const AuditLogsFilter: FC
= ({
+ filter,
+ onFilterChange,
+}) => {
+ const [localFilter, setLocalFilter] = useState(filter);
+
+ const handleFilterChange = (key: keyof AuditEventFilter, value: any) => {
+ const newFilter = { ...localFilter, [key]: value };
+ setLocalFilter(newFilter);
+ };
+
+ const applyFilter = () => {
+ onFilterChange(localFilter);
+ };
+
+ const resetFilter = () => {
+ const emptyFilter: AuditEventFilter = {};
+ setLocalFilter(emptyFilter);
+ onFilterChange(emptyFilter);
+ };
+
+ return (
+
+
+ Filter Audit Logs
+
+
+
+
+ Target Type
+
+ handleFilterChange("target_type", e.target.value || undefined)
+ }
+ className={styles.select}
+ >
+ {TARGET_TYPES.map((option) => (
+
+ {option.label}
+
+ ))}
+
+
+
+
+ Action
+
+ handleFilterChange("action", e.target.value || undefined)
+ }
+ className={styles.select}
+ >
+ {ACTIONS.map((option) => (
+
+ {option.label}
+
+ ))}
+
+
+
+
+ Outcome
+
+ handleFilterChange("outcome", e.target.value || undefined)
+ }
+ className={styles.select}
+ >
+ {OUTCOMES.map((option) => (
+
+ {option.label}
+
+ ))}
+
+
+
+
+ Target ID
+
+ handleFilterChange("target_id", e.target.value || undefined)
+ }
+ placeholder="Enter target ID"
+ />
+
+
+
+ Actor
+
+ handleFilterChange("actor", e.target.value || undefined)
+ }
+ placeholder="Enter actor"
+ />
+
+
+
+
+
+ Reset
+
+ Apply Filter
+
+
+ );
+};
diff --git a/apps/oko_admin_web/src/components/audit_logs/audit_logs_table.module.scss b/apps/oko_admin_web/src/components/audit_logs/audit_logs_table.module.scss
new file mode 100644
index 000000000..bc3981d54
--- /dev/null
+++ b/apps/oko_admin_web/src/components/audit_logs/audit_logs_table.module.scss
@@ -0,0 +1,52 @@
+.container {
+ width: 100%;
+ overflow-x: auto;
+}
+
+.targetId {
+ font-family: monospace;
+ font-size: 0.875rem;
+ color: var(--gray-600);
+ word-break: break-all;
+}
+
+.userAgent {
+ font-size: 0.75rem;
+ color: var(--gray-600);
+ word-break: break-all;
+ max-width: 200px;
+}
+
+.changes {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+ max-width: 200px;
+}
+
+.change {
+ display: flex;
+ flex-direction: column;
+ gap: 0.125rem;
+ padding: 0.25rem;
+ background-color: var(--gray-50);
+ border-radius: 0.25rem;
+ font-size: 0.75rem;
+}
+
+.params {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+ max-width: 200px;
+}
+
+.param {
+ display: flex;
+ flex-direction: column;
+ gap: 0.125rem;
+ padding: 0.25rem;
+ background-color: var(--gray-50);
+ border-radius: 0.25rem;
+ font-size: 0.75rem;
+}
diff --git a/apps/oko_admin_web/src/components/audit_logs/audit_logs_table.tsx b/apps/oko_admin_web/src/components/audit_logs/audit_logs_table.tsx
new file mode 100644
index 000000000..367e388b4
--- /dev/null
+++ b/apps/oko_admin_web/src/components/audit_logs/audit_logs_table.tsx
@@ -0,0 +1,136 @@
+"use client";
+
+import { type FC } from "react";
+import type { AuditEvent } from "@oko-wallet/oko-types/admin";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeaderCell,
+ TableRow,
+} from "@oko-wallet/oko-common-ui/table";
+import { Typography } from "@oko-wallet/oko-common-ui/typography";
+import { Badge } from "@oko-wallet/oko-common-ui/badge";
+
+import styles from "./audit_logs_table.module.scss";
+
+interface AuditLogsTableProps {
+ auditLogs: AuditEvent[];
+}
+
+export const AuditLogsTable: FC = ({ auditLogs }) => {
+ const formatChanges = (changes: any[]) => {
+ if (!changes || changes.length === 0) {
+ return - ;
+ }
+
+ return (
+
+ {changes.map((change, index) => (
+
+ {change.field}:
+
+ {change.from !== undefined ? `${change.from} â ` : ""}
+ {change.to}
+
+
+ ))}
+
+ );
+ };
+
+ const formatParams = (params: Record) => {
+ if (!params || Object.keys(params).length === 0) {
+ return - ;
+ }
+
+ return (
+
+ {Object.entries(params).map(([key, value], index) => (
+
+ {key}:
+ {JSON.stringify(value)}
+
+ ))}
+
+ );
+ };
+
+ const getOutcomeBadge = (outcome: string) => {
+ const color =
+ outcome === "success"
+ ? "success"
+ : outcome === "denied"
+ ? "warning"
+ : "error";
+ return ;
+ };
+
+ return (
+
+
+
+
+ Time
+ Request ID
+ Actor
+ Actor IP
+ User Agent
+ Source
+ Action
+ Target Type
+ Target ID
+ Outcome
+ Changes
+ Params
+
+
+
+ {auditLogs.map((row) => (
+
+
+
+ {new Date(row.occurred_at).toLocaleString()}
+
+
+
+
+ {row.request_id || "-"}
+
+
+
+ {row.actor}
+
+
+ {row.actor_ip || "-"}
+
+
+
+ {row.user_agent || "-"}
+
+
+
+ {row.source}
+
+
+ {row.action}
+
+
+ {row.target_type}
+
+
+
+ {row.target_id || "-"}
+
+
+ {getOutcomeBadge(row.outcome)}
+ {formatChanges(row.changes || [])}
+ {formatParams(row.params || {})}
+
+ ))}
+
+
+
+ );
+};
diff --git a/apps/oko_admin_web/src/components/audit_logs/audit_logs_view.module.scss b/apps/oko_admin_web/src/components/audit_logs/audit_logs_view.module.scss
new file mode 100644
index 000000000..eed49da6a
--- /dev/null
+++ b/apps/oko_admin_web/src/components/audit_logs/audit_logs_view.module.scss
@@ -0,0 +1,45 @@
+.wrapper {
+ flex-grow: 1;
+ display: flex;
+ padding: 0px 40px;
+ padding-bottom: 40px;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 32px;
+ overflow: auto;
+}
+
+.loading {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 2rem;
+ background-color: var(--white);
+ border-radius: 0.5rem;
+ border: 1px solid var(--gray-200);
+ width: 100%;
+}
+
+.content {
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+ width: 100%;
+}
+
+.tableContainer {
+ background-color: var(--white);
+ border-radius: 0.5rem;
+ border: 1px solid var(--gray-200);
+ overflow: hidden;
+ width: 100%;
+}
+
+.emptyContainer {
+ width: 100%;
+}
+
+.paginationContainer {
+ display: flex;
+ justify-content: center;
+}
diff --git a/apps/oko_admin_web/src/components/audit_logs/audit_logs_view.tsx b/apps/oko_admin_web/src/components/audit_logs/audit_logs_view.tsx
new file mode 100644
index 000000000..57f86bbcf
--- /dev/null
+++ b/apps/oko_admin_web/src/components/audit_logs/audit_logs_view.tsx
@@ -0,0 +1,85 @@
+"use client";
+
+import { type FC, useState } from "react";
+import { AuditLogsTable } from "./audit_logs_table";
+import { AuditLogsFilter } from "./audit_logs_filter";
+import { TitleHeader } from "@oko-wallet-admin/components/title_header/title_header";
+import { Pagination } from "@oko-wallet-admin/components/pagination/pagination";
+import { EmptyState } from "@oko-wallet-admin/components/empty_state/empty_state";
+import { Typography } from "@oko-wallet/oko-common-ui/typography";
+import { useGetAuditLogs } from "./use_get_audit_logs";
+import type { AuditEventFilter } from "@oko-wallet/oko-types/admin";
+import { useAppState } from "@oko-wallet-admin/state";
+
+import styles from "./audit_logs_view.module.scss";
+
+const DEFAULT_LIMIT = 20;
+
+export const AuditLogsView: FC = () => {
+ const { token } = useAppState();
+ const [filter, setFilter] = useState({
+ limit: DEFAULT_LIMIT,
+ offset: 0,
+ });
+
+ const { auditLogs, isLoading, refetch, pagination, totalCount } =
+ useGetAuditLogs(filter, token || undefined);
+
+ const handleFilterChange = (newFilter: AuditEventFilter) => {
+ setFilter({
+ ...newFilter,
+ limit: DEFAULT_LIMIT,
+ offset: 0,
+ });
+ };
+
+ const handlePageChange = (page: number) => {
+ setFilter((prev) => ({
+ ...prev,
+ offset: (page - 1) * DEFAULT_LIMIT,
+ }));
+ };
+
+ if (isLoading && auditLogs.length === 0) {
+ return (
+
+
+
+ Loading audit logs...
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+ {auditLogs.length === 0 ? (
+
+
+
+ ) : (
+ <>
+
+
+
+ >
+ )}
+
+
+ );
+};
diff --git a/apps/oko_admin_web/src/components/audit_logs/use_get_audit_logs.ts b/apps/oko_admin_web/src/components/audit_logs/use_get_audit_logs.ts
new file mode 100644
index 000000000..2270f3eb2
--- /dev/null
+++ b/apps/oko_admin_web/src/components/audit_logs/use_get_audit_logs.ts
@@ -0,0 +1,56 @@
+"use client";
+
+import { useQuery } from "@tanstack/react-query";
+import { useMemo } from "react";
+import type { AuditEventFilter } from "@oko-wallet/oko-types/admin";
+import { getAuditLogs, getAuditLogsCount } from "@oko-wallet-admin/fetch/audit";
+import {
+ useTable,
+ useTablePagination,
+} from "@oko-wallet-admin/components/table/use_table";
+
+const DEFAULT_LIMIT = 20;
+
+export const useGetAuditLogs = (
+ filter: AuditEventFilter = {},
+ token?: string,
+) => {
+ const { data, isLoading, refetch } = useQuery({
+ queryKey: ["auditLogs", filter, token],
+ queryFn: () => getAuditLogs(filter, token),
+ staleTime: 1000 * 60, // 1 minute
+ });
+
+ const { data: countData, isLoading: isCountLoading } = useQuery({
+ queryKey: ["auditLogsCount", filter, token],
+ queryFn: () => getAuditLogsCount(filter, token),
+ staleTime: 1000 * 60, // 1 minute
+ });
+
+ const auditLogs = useMemo(
+ () => (data && data.success ? data.data.audit_logs : []),
+ [data],
+ );
+ const totalCount = useMemo(
+ () => (countData && countData.success ? countData.data.count : 0),
+ [countData],
+ );
+
+ const table = useTable({
+ data: auditLogs,
+ columns: [],
+ });
+
+ const pagination = useTablePagination({
+ initialPageSize: DEFAULT_LIMIT,
+ });
+
+ return {
+ auditLogs,
+ isLoading: isLoading || isCountLoading,
+ refetch,
+ table,
+ pagination,
+ totalCount,
+ };
+};
diff --git a/apps/oko_admin_web/src/components/global_header/global_header.module.scss b/apps/oko_admin_web/src/components/global_header/global_header.module.scss
index a3f8cd977..ec8bfee6e 100644
--- a/apps/oko_admin_web/src/components/global_header/global_header.module.scss
+++ b/apps/oko_admin_web/src/components/global_header/global_header.module.scss
@@ -1,5 +1,5 @@
.wrapper {
- height: 64px;
+ height: 54px;
display: flex;
flex-shrink: 0;
flex-grow: 0;
diff --git a/apps/oko_admin_web/src/components/navigation/nav_items.tsx b/apps/oko_admin_web/src/components/navigation/nav_items.tsx
index fa7ff44a4..c3eb424fe 100644
--- a/apps/oko_admin_web/src/components/navigation/nav_items.tsx
+++ b/apps/oko_admin_web/src/components/navigation/nav_items.tsx
@@ -19,8 +19,9 @@ export const navigationItems: NavigationItem[] = [
},
{
label: "Manage Users",
- route: paths.user_list,
+ route: "manage-users-menu",
icon: ,
+ subItems: [{ label: "User List", route: paths.user_list }],
},
{
label: "Sig Shares",
@@ -43,4 +44,10 @@ export const navigationItems: NavigationItem[] = [
},
],
},
+ {
+ label: "Audit Logs",
+ route: "audit-logs",
+ icon: ,
+ subItems: [{ label: "Audit History", route: paths.audit_logs }],
+ },
];
diff --git a/apps/oko_admin_web/src/components/navigation/navigation.tsx b/apps/oko_admin_web/src/components/navigation/navigation.tsx
index 28e721157..cb4cf309e 100644
--- a/apps/oko_admin_web/src/components/navigation/navigation.tsx
+++ b/apps/oko_admin_web/src/components/navigation/navigation.tsx
@@ -1,6 +1,5 @@
"use client";
-import { useEffect, useState } from "react";
import { usePathname, useRouter } from "next/navigation";
import styles from "./navigation.module.scss";
@@ -20,32 +19,9 @@ export interface NavigationProps {
}
export const Navigation: React.FC = () => {
- const [openMenus, setOpenMenus] = useState>({});
const router = useRouter();
const pathname = usePathname();
- useEffect(() => {
- const newOpenMenus: Record = {};
- navigationItems.forEach((item) => {
- if (item.subItems) {
- const isCurrentPath = item.subItems.some(
- (subItem) => pathname === subItem.route,
- );
- if (isCurrentPath) {
- newOpenMenus[item.route] = true;
- }
- }
- });
- setOpenMenus(newOpenMenus);
- }, [pathname]);
-
- const toggleMenu = (route: string) => {
- setOpenMenus((prev) => ({
- ...prev,
- [route]: !prev[route],
- }));
- };
-
return (
{navigationItems.map((item) => {
@@ -53,27 +29,20 @@ export const Navigation: React.FC
= () => {
return (
// has sub items
- toggleMenu(item.route)}
- icon={item.icon}
- >
+
{item.label}
- {openMenus[item.route] && (
-
- {item.subItems.map((subItem) => (
- router.push(subItem.route)}
- active={pathname === subItem.route}
- >
- {subItem.label}
-
- ))}
-
- )}
+
+ {item.subItems.map((subItem) => (
+ router.push(subItem.route)}
+ active={pathname === subItem.route}
+ >
+ {subItem.label}
+
+ ))}
+
);
} else {
diff --git a/apps/oko_admin_web/src/fetch/audit.ts b/apps/oko_admin_web/src/fetch/audit.ts
new file mode 100644
index 000000000..38aaa57bb
--- /dev/null
+++ b/apps/oko_admin_web/src/fetch/audit.ts
@@ -0,0 +1,70 @@
+import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
+import type {
+ AuditEventFilter,
+ GetAuditLogsResponse,
+ GetAuditLogsCountResponse,
+} from "@oko-wallet/oko-types/admin";
+
+import { OKO_ADMIN_API_ENDPOINT_V1 } from "@oko-wallet-admin/fetch";
+import { errorHandle } from "@oko-wallet-admin/fetch/utils";
+
+export const getAuditLogs = async (
+ filter: AuditEventFilter,
+ token?: string,
+): Promise> => {
+ const params = new URLSearchParams();
+
+ if (filter.target_type) params.append("target_type", filter.target_type);
+ if (filter.target_id) params.append("target_id", filter.target_id);
+ if (filter.action) params.append("action", filter.action);
+ if (filter.actor) params.append("actor", filter.actor);
+ if (filter.source) params.append("source", filter.source);
+ if (filter.outcome) params.append("outcome", filter.outcome);
+ if (filter.occurred_after)
+ params.append("occurred_after", filter.occurred_after.toISOString());
+ if (filter.occurred_before)
+ params.append("occurred_before", filter.occurred_before.toISOString());
+ if (filter.limit) params.append("limit", filter.limit.toString());
+ if (filter.offset) params.append("offset", filter.offset.toString());
+
+ return errorHandle(() =>
+ fetch(`${OKO_ADMIN_API_ENDPOINT_V1}/audit/logs?${params.toString()}`, {
+ method: "GET",
+ headers: {
+ "content-type": "application/json",
+ Authorization: `Bearer ${token}`,
+ },
+ }),
+ );
+};
+
+export const getAuditLogsCount = async (
+ filter: AuditEventFilter,
+ token?: string,
+): Promise> => {
+ const params = new URLSearchParams();
+
+ if (filter.target_type) params.append("target_type", filter.target_type);
+ if (filter.target_id) params.append("target_id", filter.target_id);
+ if (filter.action) params.append("action", filter.action);
+ if (filter.actor) params.append("actor", filter.actor);
+ if (filter.source) params.append("source", filter.source);
+ if (filter.outcome) params.append("outcome", filter.outcome);
+ if (filter.occurred_after)
+ params.append("occurred_after", filter.occurred_after.toISOString());
+ if (filter.occurred_before)
+ params.append("occurred_before", filter.occurred_before.toISOString());
+
+ return errorHandle(() =>
+ fetch(
+ `${OKO_ADMIN_API_ENDPOINT_V1}/audit/logs/count?${params.toString()}`,
+ {
+ method: "GET",
+ headers: {
+ "content-type": "application/json",
+ Authorization: `Bearer ${token}`,
+ },
+ },
+ ),
+ );
+};
diff --git a/apps/oko_admin_web/src/paths.ts b/apps/oko_admin_web/src/paths.ts
index 723992669..db93ebcfd 100644
--- a/apps/oko_admin_web/src/paths.ts
+++ b/apps/oko_admin_web/src/paths.ts
@@ -7,4 +7,5 @@ export const paths = {
ks_nodes_edit: "/keyshare_nodes/edit",
sign_in: "/users/sign_in",
user_list: "/users/list",
+ audit_logs: "/audit_logs",
};
diff --git a/backend/admin_api/src/api/audit/index.ts b/backend/admin_api/src/api/audit/index.ts
new file mode 100644
index 000000000..1bc9117de
--- /dev/null
+++ b/backend/admin_api/src/api/audit/index.ts
@@ -0,0 +1,69 @@
+import { Pool } from "pg";
+import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
+import type { AuditEventFilter } from "@oko-wallet/oko-types/admin";
+import type {
+ GetAuditLogsResponse,
+ GetAuditLogsCountResponse,
+} from "@oko-wallet/oko-types/admin";
+import {
+ getAuditEvents,
+ getAuditEventsCount,
+} from "@oko-wallet/oko-pg-interface/audit_events";
+
+export async function getAuditLogs(
+ db: Pool,
+ filter: AuditEventFilter,
+): Promise> {
+ try {
+ const getAuditEventsRes = await getAuditEvents(db, filter);
+ if (getAuditEventsRes.success === false) {
+ return {
+ success: false,
+ code: "UNKNOWN_ERROR",
+ msg: `Failed to get audit events: ${getAuditEventsRes.err}`,
+ };
+ }
+
+ return {
+ success: true,
+ data: {
+ audit_logs: getAuditEventsRes.data,
+ },
+ };
+ } catch (error) {
+ return {
+ success: false,
+ code: "UNKNOWN_ERROR",
+ msg: `Failed to get audit logs: ${error instanceof Error ? error.message : String(error)}`,
+ };
+ }
+}
+
+export async function getAuditLogsCount(
+ db: Pool,
+ filter: AuditEventFilter,
+): Promise> {
+ try {
+ const getAuditEventsCountRes = await getAuditEventsCount(db, filter);
+ if (getAuditEventsCountRes.success === false) {
+ return {
+ success: false,
+ code: "UNKNOWN_ERROR",
+ msg: `Failed to get audit events count: ${getAuditEventsCountRes.err}`,
+ };
+ }
+
+ return {
+ success: true,
+ data: {
+ count: getAuditEventsCountRes.data,
+ },
+ };
+ } catch (error) {
+ return {
+ success: false,
+ code: "UNKNOWN_ERROR",
+ msg: `Failed to get audit logs count: ${error instanceof Error ? error.message : String(error)}`,
+ };
+ }
+}
diff --git a/backend/admin_api/src/api/customer/index.ts b/backend/admin_api/src/api/customer/index.ts
index a7b115d07..be0c8942a 100644
--- a/backend/admin_api/src/api/customer/index.ts
+++ b/backend/admin_api/src/api/customer/index.ts
@@ -32,6 +32,7 @@ import type {
APIKey,
InsertCustomerDashboardUserRequest,
} from "@oko-wallet/oko-types/ct_dashboard";
+import { createAuditLog } from "@oko-wallet-admin-api/utils/audit";
export async function createCustomer(
db: Pool,
@@ -45,7 +46,10 @@ export async function createCustomer(
};
logo?: { buffer: Buffer; originalname: string } | null;
},
+ auditContext?: { adminUserId?: string; request?: any; requestId?: string },
): Promise> {
+ const context = { db, ...auditContext };
+
try {
let logo_url: string | null = null;
if (opts.logo) {
@@ -57,13 +61,15 @@ export async function createCustomer(
key: `logos/${Date.now().toString()}-${opts.logo.originalname}`,
body: opts.logo.buffer,
});
- if (uploadRes.success === false) {
+
+ if (!uploadRes.success) {
return {
success: false,
code: "UNKNOWN_ERROR",
msg: `Failed to upload logo to S3: ${uploadRes.err}`,
};
}
+
logo_url = decodeURIComponent(uploadRes.data);
}
@@ -113,8 +119,8 @@ export async function createCustomer(
await client.query("COMMIT");
- return {
- success: true,
+ const result = {
+ success: true as const,
data: {
customer_id,
label: insertCustomerRes.data.label,
@@ -125,8 +131,11 @@ export async function createCustomer(
message: "Customer created successfully",
},
};
+
+ return result;
} catch (error) {
await client.query("ROLLBACK");
+
return {
success: false,
code: "UNKNOWN_ERROR",
@@ -253,12 +262,15 @@ export async function getCustomerById(
export async function deleteCustomerAndUsers(
db: Pool,
customer_id: string,
+ auditContext?: { adminUserId?: string; request?: any; requestId?: string },
): Promise<
OkoApiResponse<{
customer_id: string;
customer_dashboard_user_ids: string[];
}>
> {
+ const context = { db, ...auditContext };
+
try {
const client = await db.connect();
try {
@@ -290,16 +302,19 @@ export async function deleteCustomerAndUsers(
await client.query("COMMIT");
- return {
- success: true,
+ const result = {
+ success: true as const,
data: {
customer_id: deleteCustomerRes.data.customer_id,
customer_dashboard_user_ids:
deleteCustomerDashboardUserRes.data.customer_dashboard_user_ids,
},
};
+
+ return result;
} catch (error) {
await client.query("ROLLBACK");
+
return {
success: false,
code: "UNKNOWN_ERROR",
diff --git a/backend/admin_api/src/api/ks_node/index.ts b/backend/admin_api/src/api/ks_node/index.ts
index 129e7615e..5381a0729 100644
--- a/backend/admin_api/src/api/ks_node/index.ts
+++ b/backend/admin_api/src/api/ks_node/index.ts
@@ -25,6 +25,8 @@ import {
} from "@oko-wallet/oko-pg-interface/ks_nodes";
import type { KSNodeStatus } from "@oko-wallet-types/tss";
import { processKSNodeHealthChecks } from "@oko-wallet/ks-node-health";
+import { createAuditLog } from "@oko-wallet-admin-api/utils/audit";
+import type { AuditContext } from "@oko-wallet-admin-api/utils/audit";
export async function getAllKSNodes(
db: Pool,
@@ -49,7 +51,7 @@ export async function getAllKSNodes(
return {
success: false,
code: "UNKNOWN_ERROR",
- msg: `Failed to get ksNodes: ${error instanceof Error ? error.message : String(error)}`,
+ msg: `Failed to get ksNodes, err: ${error}`,
};
}
}
@@ -80,11 +82,11 @@ export async function getKSNodeById(
success: true,
data: { ksNode: getKSNodeRes.data },
};
- } catch (error) {
+ } catch (err: any) {
return {
success: false,
code: "UNKNOWN_ERROR",
- msg: `Failed to get ksNode: ${error instanceof Error ? error.message : String(error)}`,
+ msg: `Failed to get ksNode: err: ${err}`,
};
}
}
@@ -92,6 +94,7 @@ export async function getKSNodeById(
export async function createKSNode(
db: Pool,
body: CreateKSNodeRequest,
+ auditContext?: AuditContext,
): Promise> {
try {
const { node_name, server_url } = body;
@@ -122,11 +125,11 @@ export async function createKSNode(
success: true,
data: { node_id },
};
- } catch (error) {
+ } catch (err: any) {
return {
success: false,
code: "UNKNOWN_ERROR",
- msg: `Failed to create ksNode: ${error instanceof Error ? error.message : String(error)}`,
+ msg: `Failed to create ksNode, err: ${err}`,
};
}
}
@@ -134,6 +137,7 @@ export async function createKSNode(
export async function deactivateKSNode(
db: Pool,
body: DeactivateKSNodeRequest,
+ auditContext?: AuditContext,
): Promise> {
try {
const getKSNodeRes = await getKSNodeByIdFromPG(db, body.node_id);
@@ -146,6 +150,18 @@ export async function deactivateKSNode(
}
if (getKSNodeRes.data === null) {
+ if (auditContext) {
+ await createAuditLog(
+ auditContext,
+ "deactivate",
+ "ks_node",
+ body.node_id,
+ undefined,
+ undefined,
+ "failure",
+ `KSNode not found: ${body.node_id}`,
+ );
+ }
return {
success: false,
code: "KS_NODE_NOT_FOUND",
@@ -154,6 +170,18 @@ export async function deactivateKSNode(
}
if (getKSNodeRes.data.status === "INACTIVE") {
+ if (auditContext) {
+ await createAuditLog(
+ auditContext,
+ "deactivate",
+ "ks_node",
+ body.node_id,
+ undefined,
+ undefined,
+ "denied",
+ `KSNode already inactive: ${body.node_id}`,
+ );
+ }
return {
success: false,
code: "KS_NODE_ALREADY_INACTIVE",
@@ -174,15 +202,27 @@ export async function deactivateKSNode(
};
}
+ if (auditContext) {
+ await createAuditLog(
+ auditContext,
+ "disable",
+ "node",
+ body.node_id,
+ [{ field: "status", from: getKSNodeRes.data.status, to: "INACTIVE" }],
+ { node_id: body.node_id },
+ "success",
+ );
+ }
+
return {
success: true,
data: { node_id: body.node_id },
};
- } catch (error) {
+ } catch (err: any) {
return {
success: false,
code: "UNKNOWN_ERROR",
- msg: `Failed to deactivate ksNode: ${error instanceof Error ? error.message : String(error)}`,
+ msg: `Failed to deactivate ksNode, err: ${err}`,
};
}
}
@@ -190,6 +230,7 @@ export async function deactivateKSNode(
export async function activateKSNode(
db: Pool,
body: ActivateKSNodeRequest,
+ auditContext?: AuditContext,
): Promise> {
try {
const getKSNodeRes = await getKSNodeByIdFromPG(db, body.node_id);
@@ -230,15 +271,27 @@ export async function activateKSNode(
};
}
+ if (auditContext) {
+ await createAuditLog(
+ auditContext,
+ "enable",
+ "node",
+ body.node_id,
+ [{ field: "status", from: getKSNodeRes.data.status, to: "ACTIVE" }],
+ { node_id: body.node_id },
+ "success",
+ );
+ }
+
return {
success: true,
data: { node_id: body.node_id },
};
- } catch (error) {
+ } catch (err: any) {
return {
success: false,
code: "UNKNOWN_ERROR",
- msg: `Failed to activate ksNode: ${error instanceof Error ? error.message : String(error)}`,
+ msg: `Failed to activate ksNode, err: ${err}`,
};
}
}
@@ -246,6 +299,7 @@ export async function activateKSNode(
export async function updateKSNode(
db: Pool,
body: UpdateKSNodeRequest,
+ auditContext?: AuditContext,
): Promise> {
try {
const { node_id, server_url } = body;
@@ -258,6 +312,13 @@ export async function updateKSNode(
};
}
+ // Get current node info for audit trail
+ const getCurrentNodeRes = await getKSNodeByIdFromPG(db, node_id);
+ const oldServerUrl =
+ getCurrentNodeRes.success && getCurrentNodeRes.data
+ ? getCurrentNodeRes.data.server_url
+ : undefined;
+
const updateKSNodeRes = await updateKSNodeInfo(db, node_id, server_url);
if (updateKSNodeRes.success === false) {
return {
@@ -267,15 +328,27 @@ export async function updateKSNode(
};
}
+ if (auditContext) {
+ await createAuditLog(
+ auditContext,
+ "update",
+ "node",
+ node_id,
+ [{ field: "server_url", from: oldServerUrl, to: server_url }],
+ { node_id, server_url },
+ "success",
+ );
+ }
+
return {
success: true,
data: { node_id },
};
- } catch (error) {
+ } catch (err) {
return {
success: false,
code: "UNKNOWN_ERROR",
- msg: `Failed to update ksNode: ${error instanceof Error ? error.message : String(error)}`,
+ msg: `Failed to update ksNode: ${err}`,
};
}
}
@@ -283,6 +356,7 @@ export async function updateKSNode(
export async function deleteKSNode(
db: Pool,
body: DeleteKSNodeRequest,
+ auditContext?: AuditContext,
): Promise> {
try {
const getKSNodeRes = await getKSNodeByIdFromPG(db, body.node_id);
@@ -311,15 +385,27 @@ export async function deleteKSNode(
};
}
+ if (auditContext) {
+ await createAuditLog(
+ auditContext,
+ "delete",
+ "node",
+ body.node_id,
+ [{ field: "deleted_at", from: null, to: new Date().toISOString() }],
+ { node_id: body.node_id },
+ "success",
+ );
+ }
+
return {
success: true,
data: { node_id: body.node_id },
};
- } catch (error) {
+ } catch (err) {
return {
success: false,
code: "UNKNOWN_ERROR",
- msg: `Failed to delete ksNode: ${error instanceof Error ? error.message : String(error)}`,
+ msg: `Failed to delete ksNode: ${err}`,
};
}
}
diff --git a/backend/admin_api/src/api/tss/index.ts b/backend/admin_api/src/api/tss/index.ts
index 2b67e658c..5a5d27271 100644
--- a/backend/admin_api/src/api/tss/index.ts
+++ b/backend/admin_api/src/api/tss/index.ts
@@ -12,6 +12,8 @@ import {
getTssActivationSetting as getTssAllActivationSettingPG,
setTssActivationSetting as setTssAllActivationSettingPG,
} from "@oko-wallet/oko-pg-interface/tss_activate";
+import { createAuditLog } from "@oko-wallet-admin-api/utils/audit";
+import type { AuditContext } from "@oko-wallet-admin-api/utils/audit";
export async function getTssSessionList(
db: Pool,
@@ -110,16 +112,10 @@ export async function getTssAllActivationSetting(
export async function setTssAllActivationSetting(
db: Pool,
body: SetTssAllActivationSettingRequest,
+ auditContext?: AuditContext,
): Promise> {
try {
const { is_enabled } = body;
- if (typeof is_enabled !== "boolean") {
- return {
- success: false,
- code: "INVALID_REQUEST",
- msg: "is_enabled must be a boolean value",
- };
- }
const getTssActivationRes = await getTssAllActivationSettingPG(
db,
@@ -142,6 +138,8 @@ export async function setTssAllActivationSetting(
};
}
+ const oldValue = tssActivationSetting.is_enabled;
+
const setTssActivationRes = await setTssAllActivationSettingPG(
db,
is_enabled,
diff --git a/backend/admin_api/src/api/user/index.ts b/backend/admin_api/src/api/user/index.ts
index 8286ff892..b7b9c1555 100644
--- a/backend/admin_api/src/api/user/index.ts
+++ b/backend/admin_api/src/api/user/index.ts
@@ -9,6 +9,8 @@ import { getAdminByEmail } from "@oko-wallet/oko-pg-interface/admin_users";
import { comparePassword } from "@oko-wallet/crypto-js";
import { generateAdminToken } from "@oko-wallet-admin-api/auth";
+import { createAuditLog } from "@oko-wallet-admin-api/utils/audit";
+import type { AuditContext } from "@oko-wallet-admin-api/utils/audit";
export async function login(
db: Pool,
@@ -17,6 +19,7 @@ export async function login(
secret: string;
expires_in: string;
},
+ auditContext?: AuditContext,
): Promise> {
try {
const getAdminRes = await getAdminByEmail(db, body.email);
@@ -96,6 +99,7 @@ export async function login(
export async function logout(
db: Pool,
token?: string,
+ auditContext?: AuditContext,
): Promise> {
try {
if (token) {
diff --git a/backend/admin_api/src/api/ewallet_wallet/index.ts b/backend/admin_api/src/api/wallet/index.ts
similarity index 100%
rename from backend/admin_api/src/api/ewallet_wallet/index.ts
rename to backend/admin_api/src/api/wallet/index.ts
diff --git a/backend/admin_api/src/middleware/index.ts b/backend/admin_api/src/middleware/index.ts
index 473e212ef..42369fe32 100644
--- a/backend/admin_api/src/middleware/index.ts
+++ b/backend/admin_api/src/middleware/index.ts
@@ -1,11 +1,15 @@
import type { Request, Response, NextFunction } from "express";
+import { v4 as uuidv4 } from "uuid";
import { verifyAdminToken } from "@oko-wallet-admin-api/auth";
+import type { AuditContext } from "@oko-wallet-admin-api/utils/audit";
-export type AuthenticatedAdminRequest = Request;
+export type AuthenticatedAdminRequest = Request & {
+ auditContext?: AuditContext;
+};
export function adminAuthMiddleware(
- req: Request,
+ req: Request & { auditContext?: AuditContext },
res: Response,
next: NextFunction,
) {
@@ -43,6 +47,15 @@ export function adminAuthMiddleware(
role: verifyResult.data.role,
type: verifyResult.data.type,
};
+
+ const auditContext: AuditContext = {
+ db: req.app.locals.db,
+ adminUserId: verifyResult.data.sub,
+ request: req,
+ requestId: uuidv4(),
+ };
+
+ req.auditContext = auditContext;
next();
return;
}
diff --git a/backend/admin_api/src/routes/activate_ks_node.ts b/backend/admin_api/src/routes/activate_ks_node.ts
new file mode 100644
index 000000000..94142f02f
--- /dev/null
+++ b/backend/admin_api/src/routes/activate_ks_node.ts
@@ -0,0 +1,25 @@
+import type { Response } from "express";
+import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
+import type {
+ ActivateKSNodeRequest,
+ ActivateKSNodeResponse,
+} from "@oko-wallet/oko-types/admin";
+import { ErrorCodeMap } from "@oko-wallet/oko-api-error-codes";
+
+import { type AuthenticatedAdminRequest } from "@oko-wallet-admin-api/middleware";
+import { activateKSNode } from "@oko-wallet-admin-api/api/ks_node";
+
+export async function activate_ks_node(
+ req: AuthenticatedAdminRequest,
+ res: Response>,
+) {
+ const state = req.app.locals;
+
+ const result = await activateKSNode(state.db, req.body, req.auditContext);
+ if (!result.success) {
+ res.status(ErrorCodeMap[result.code] ?? 500).json(result);
+ return;
+ }
+
+ res.status(200).json(result);
+}
diff --git a/backend/admin_api/src/routes/create_customer.ts b/backend/admin_api/src/routes/create_customer.ts
new file mode 100644
index 000000000..9fdb02cb1
--- /dev/null
+++ b/backend/admin_api/src/routes/create_customer.ts
@@ -0,0 +1,68 @@
+import type { Response } from "express";
+import type {
+ CreateCustomerResponse,
+ CreateCustomerWithDashboardUserRequest,
+} from "@oko-wallet/oko-types/admin";
+import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
+import { ErrorCodeMap } from "@oko-wallet/oko-api-error-codes";
+
+import { type AuthenticatedAdminRequest } from "@oko-wallet-admin-api/middleware";
+import { createCustomer } from "@oko-wallet-admin-api/api/customer";
+
+export async function create_customer(
+ req: AuthenticatedAdminRequest & {
+ file?: Express.Multer.File;
+ },
+ res: Response>,
+) {
+ const body = req.body;
+ const state = req.app.locals;
+
+ if (!body || !body.email || !body.password || !body.label) {
+ res.status(400).json({
+ success: false,
+ code: "INVALID_EMAIL_OR_PASSWORD",
+ msg: "Email, password, and label are required",
+ });
+ return;
+ }
+
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+ if (!emailRegex.test(body.email)) {
+ res.status(400).json({
+ success: false,
+ code: "INVALID_EMAIL_OR_PASSWORD",
+ msg: "Invalid email format",
+ });
+ return;
+ }
+
+ const createCustomerRes = await createCustomer(
+ state.db,
+ body,
+ {
+ s3: {
+ region: state.s3_region,
+ accessKeyId: state.s3_access_key_id,
+ secretAccessKey: state.s3_secret_access_key,
+ bucket: state.s3_bucket,
+ },
+ logo: req.file
+ ? { buffer: req.file.buffer, originalname: req.file.originalname }
+ : null,
+ },
+ req.auditContext,
+ );
+ if (createCustomerRes.success === false) {
+ res
+ .status(ErrorCodeMap[createCustomerRes.code] ?? 500)
+ .json(createCustomerRes);
+ return;
+ }
+
+ res.status(201).json({
+ success: true,
+ data: createCustomerRes.data,
+ });
+ return;
+}
diff --git a/backend/admin_api/src/routes/create_ks_node.ts b/backend/admin_api/src/routes/create_ks_node.ts
new file mode 100644
index 000000000..ab415fac7
--- /dev/null
+++ b/backend/admin_api/src/routes/create_ks_node.ts
@@ -0,0 +1,23 @@
+import type { Response } from "express";
+import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
+import type { CreateKSNodeResponse } from "@oko-wallet/oko-types/admin";
+import type { CreateKSNodeRequest } from "@oko-wallet/oko-types/admin";
+import { ErrorCodeMap } from "@oko-wallet/oko-api-error-codes";
+
+import { type AuthenticatedAdminRequest } from "@oko-wallet-admin-api/middleware";
+import { createKSNode } from "@oko-wallet-admin-api/api/ks_node";
+
+export async function create_ks_node(
+ req: AuthenticatedAdminRequest,
+ res: Response>,
+) {
+ const state = req.app.locals;
+
+ const result = await createKSNode(state.db, req.body, req.auditContext);
+ if (!result.success) {
+ res.status(ErrorCodeMap[result.code] ?? 500).json(result);
+ return;
+ }
+
+ res.status(200).json(result);
+}
diff --git a/backend/admin_api/src/routes/customer.ts b/backend/admin_api/src/routes/customer.ts
deleted file mode 100644
index 4064901e2..000000000
--- a/backend/admin_api/src/routes/customer.ts
+++ /dev/null
@@ -1,406 +0,0 @@
-import type { Response, Router } from "express";
-import type {
- CreateCustomerResponse,
- CreateCustomerWithDashboardUserRequest,
-} from "@oko-wallet/oko-types/admin";
-import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
-import type {
- Customer,
- CustomerWithAPIKeys,
- DeleteCustomerAndCustomerDashboardUsersRequest,
- DeleteCustomerAndCustomerDashboardUsersResponse,
-} from "@oko-wallet/oko-types/customers";
-import multer from "multer";
-import { ErrorCodeMap } from "@oko-wallet/oko-api-error-codes";
-import { registry } from "@oko-wallet/oko-api-openapi";
-import {
- ErrorResponseSchema,
- AdminAuthHeaderSchema,
-} from "@oko-wallet/oko-api-openapi/common";
-import {
- CreateCustomerWithDashboardUserRequestSchema,
- CreateCustomerSuccessResponseSchema,
- GetCustomerListQuerySchema,
- GetCustomerListSuccessResponseSchema,
- CustomerIdParamSchema,
- GetCustomerSuccessResponseSchema,
- DeleteCustomerSuccessResponseSchema,
-} from "@oko-wallet/oko-api-openapi/oko_admin";
-
-import {
- adminAuthMiddleware,
- type AuthenticatedAdminRequest,
-} from "@oko-wallet-admin-api/middleware";
-import {
- createCustomer,
- getCustomerList,
- getCustomerById,
- deleteCustomerAndUsers,
-} from "@oko-wallet-admin-api/api/customer";
-
-const upload = multer();
-
-export function setCustomerRoutes(router: Router) {
- registry.registerPath({
- method: "post",
- path: "/oko_admin/v1/customer/create_customer",
- tags: ["Admin"],
- summary: "Create new customer",
- description: "Creates a new customer with dashboard user account",
- security: [{ adminAuth: [] }],
- request: {
- headers: AdminAuthHeaderSchema,
- body: {
- content: {
- "multipart/form-data": {
- schema: CreateCustomerWithDashboardUserRequestSchema,
- },
- },
- },
- },
- responses: {
- 201: {
- description: "Customer created successfully",
- content: {
- "application/json": {
- schema: CreateCustomerSuccessResponseSchema,
- },
- },
- },
- 400: {
- description: "Invalid request",
- content: {
- "application/json": {
- schema: ErrorResponseSchema,
- },
- },
- },
- 401: {
- description: "Unauthorized",
- content: {
- "application/json": {
- schema: ErrorResponseSchema,
- },
- },
- },
- 500: {
- description: "Server error",
- content: {
- "application/json": {
- schema: ErrorResponseSchema,
- },
- },
- },
- },
- });
- router.post(
- "/customer/create_customer",
- adminAuthMiddleware,
- upload.single("logo"),
- async (
- req: AuthenticatedAdminRequest & {
- file?: Express.Multer.File;
- },
- res: Response>,
- ) => {
- const body = req.body;
- const state = req.app.locals;
-
- if (!body || !body.email || !body.password || !body.label) {
- res.status(400).json({
- success: false,
- code: "INVALID_EMAIL_OR_PASSWORD",
- msg: "Email, password, and label are required",
- });
- return;
- }
-
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
- if (!emailRegex.test(body.email)) {
- res.status(400).json({
- success: false,
- code: "INVALID_EMAIL_OR_PASSWORD",
- msg: "Invalid email format",
- });
- return;
- }
-
- const createCustomerRes = await createCustomer(state.db, body, {
- s3: {
- region: state.s3_region,
- accessKeyId: state.s3_access_key_id,
- secretAccessKey: state.s3_secret_access_key,
- bucket: state.s3_bucket,
- },
- logo: req.file
- ? { buffer: req.file.buffer, originalname: req.file.originalname }
- : null,
- });
- if (createCustomerRes.success === false) {
- res
- .status(ErrorCodeMap[createCustomerRes.code] ?? 500)
- .json(createCustomerRes);
- return;
- }
-
- res.status(201).json({
- success: true,
- data: createCustomerRes.data,
- });
- return;
- },
- );
-
- registry.registerPath({
- method: "get",
- path: "/oko_admin/v1/customer/get_customer_list",
- tags: ["Admin"],
- summary: "Get customers with pagination",
- description: "Retrieves a list of customers with pagination",
- security: [{ adminAuth: [] }],
- request: {
- headers: AdminAuthHeaderSchema,
- query: GetCustomerListQuerySchema,
- },
- responses: {
- 200: {
- description: "Customer list retrieved successfully",
- content: {
- "application/json": {
- schema: GetCustomerListSuccessResponseSchema,
- },
- },
- },
- 401: {
- description: "Unauthorized",
- content: {
- "application/json": {
- schema: ErrorResponseSchema,
- },
- },
- },
- 500: {
- description: "Server error",
- content: {
- "application/json": {
- schema: ErrorResponseSchema,
- },
- },
- },
- },
- });
- router.get(
- "/customer/get_customer_list",
- adminAuthMiddleware,
- async (
- req: AuthenticatedAdminRequest,
- res: Response<
- OkoApiResponse<{
- customerWithAPIKeysList: CustomerWithAPIKeys[];
- pagination: {
- total: number;
- current_page: number;
- total_pages: number;
- };
- }>
- >,
- ) => {
- const state = req.app.locals;
-
- let { limit, offset } = req.query;
- if (!limit || !offset) {
- limit = 10;
- offset = 0;
- } else {
- limit = parseInt(limit);
- offset = parseInt(offset);
- }
-
- const getCustomerListRes = await getCustomerList(state.db, limit, offset);
- if (getCustomerListRes.success === false) {
- res
- .status(ErrorCodeMap[getCustomerListRes.code] ?? 500)
- .json(getCustomerListRes);
- return;
- }
-
- res.status(200).json({
- success: true,
- data: getCustomerListRes.data,
- });
- return;
- },
- );
-
- registry.registerPath({
- method: "get",
- path: "/oko_admin/v1/customer/get_customer/{customer_id}",
- tags: ["Admin"],
- summary: "Get customer by ID",
- description: "Retrieves customer information by customer ID",
- security: [{ adminAuth: [] }],
- request: {
- headers: AdminAuthHeaderSchema,
- params: CustomerIdParamSchema,
- },
- responses: {
- 200: {
- description: "Customer information retrieved successfully",
- content: {
- "application/json": {
- schema: GetCustomerSuccessResponseSchema,
- },
- },
- },
- 400: {
- description: "Invalid request",
- content: {
- "application/json": {
- schema: ErrorResponseSchema,
- },
- },
- },
- 401: {
- description: "Unauthorized",
- content: {
- "application/json": {
- schema: ErrorResponseSchema,
- },
- },
- },
- 404: {
- description: "Customer not found",
- content: {
- "application/json": {
- schema: ErrorResponseSchema,
- },
- },
- },
- 500: {
- description: "Server error",
- content: {
- "application/json": {
- schema: ErrorResponseSchema,
- },
- },
- },
- },
- });
- router.get(
- "/customer/get_customer/:customer_id",
- adminAuthMiddleware,
- async (
- req: AuthenticatedAdminRequest,
- res: Response>,
- ) => {
- const state = req.app.locals;
- const { customer_id: customerId } = req.params;
-
- if (!customerId) {
- res.status(400).json({
- success: false,
- code: "MISSING_CUSTOMER_ID",
- msg: "Customer ID is required",
- });
- return;
- }
-
- const getCustomerByIdRes = await getCustomerById(state.db, customerId);
- if (getCustomerByIdRes.success === false) {
- res
- .status(ErrorCodeMap[getCustomerByIdRes.code] ?? 500)
- .json(getCustomerByIdRes);
- return;
- }
-
- res.status(200).json({ success: true, data: getCustomerByIdRes.data });
- return;
- },
- );
-
- registry.registerPath({
- method: "post",
- path: "/oko_admin/v1/customer/delete_customer/{customer_id}",
- tags: ["Admin"],
- summary: "Delete customer by ID",
- description: "Deletes a customer by customer ID",
- security: [{ adminAuth: [] }],
- request: {
- headers: AdminAuthHeaderSchema,
- params: CustomerIdParamSchema,
- },
- responses: {
- 200: {
- description: "Customer deleted successfully",
- content: {
- "application/json": {
- schema: DeleteCustomerSuccessResponseSchema,
- },
- },
- },
- 400: {
- description: "Invalid request",
- content: {
- "application/json": {
- schema: ErrorResponseSchema,
- },
- },
- },
- 401: {
- description: "Unauthorized",
- content: {
- "application/json": {
- schema: ErrorResponseSchema,
- },
- },
- },
- 500: {
- description: "Server error",
- content: {
- "application/json": {
- schema: ErrorResponseSchema,
- },
- },
- },
- },
- });
- router.post(
- "/customer/delete_customer/:customer_id",
- adminAuthMiddleware,
- async (
- req: AuthenticatedAdminRequest,
- res: Response<
- OkoApiResponse
- >,
- ) => {
- const state = req.app.locals;
- const { customer_id: customerId } = req.params;
-
- if (!customerId) {
- res.status(400).json({
- success: false,
- code: "MISSING_CUSTOMER_ID",
- msg: "Customer ID is required",
- });
- return;
- }
-
- const deleteCustomerAndUsersRes = await deleteCustomerAndUsers(
- state.db,
- customerId,
- );
- if (deleteCustomerAndUsersRes.success === false) {
- res
- .status(ErrorCodeMap[deleteCustomerAndUsersRes.code] ?? 500)
- .json(deleteCustomerAndUsersRes);
- return;
- }
-
- res.status(200).json({
- success: true,
- data: deleteCustomerAndUsersRes.data,
- });
- return;
- },
- );
-}
diff --git a/backend/admin_api/src/routes/deactivate_ks_node.ts b/backend/admin_api/src/routes/deactivate_ks_node.ts
new file mode 100644
index 000000000..997c00dac
--- /dev/null
+++ b/backend/admin_api/src/routes/deactivate_ks_node.ts
@@ -0,0 +1,25 @@
+import type { Response } from "express";
+import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
+import type {
+ DeactivateKSNodeRequest,
+ DeactivateKSNodeResponse,
+} from "@oko-wallet/oko-types/admin";
+import { ErrorCodeMap } from "@oko-wallet/oko-api-error-codes";
+
+import { type AuthenticatedAdminRequest } from "@oko-wallet-admin-api/middleware";
+import { deactivateKSNode } from "@oko-wallet-admin-api/api/ks_node";
+
+export async function deactivate_ks_node(
+ req: AuthenticatedAdminRequest,
+ res: Response>,
+) {
+ const state = req.app.locals;
+
+ const result = await deactivateKSNode(state.db, req.body, req.auditContext);
+ if (!result.success) {
+ res.status(ErrorCodeMap[result.code] ?? 500).json(result);
+ return;
+ }
+
+ res.status(200).json(result);
+}
diff --git a/backend/admin_api/src/routes/delete_customer.ts b/backend/admin_api/src/routes/delete_customer.ts
new file mode 100644
index 000000000..15706c339
--- /dev/null
+++ b/backend/admin_api/src/routes/delete_customer.ts
@@ -0,0 +1,47 @@
+import type { Response } from "express";
+import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
+import type {
+ DeleteCustomerAndCustomerDashboardUsersRequest,
+ DeleteCustomerAndCustomerDashboardUsersResponse,
+} from "@oko-wallet/oko-types/customers";
+import { ErrorCodeMap } from "@oko-wallet/oko-api-error-codes";
+
+import { type AuthenticatedAdminRequest } from "@oko-wallet-admin-api/middleware";
+import { deleteCustomerAndUsers } from "@oko-wallet-admin-api/api/customer";
+
+export async function delete_customer(
+ req: AuthenticatedAdminRequest,
+ res: Response<
+ OkoApiResponse
+ >,
+) {
+ const state = req.app.locals;
+ const { customer_id: customerId } = req.params;
+
+ if (!customerId) {
+ res.status(400).json({
+ success: false,
+ code: "MISSING_CUSTOMER_ID",
+ msg: "Customer ID is required",
+ });
+ return;
+ }
+
+ const deleteCustomerAndUsersRes = await deleteCustomerAndUsers(
+ state.db,
+ customerId,
+ req.auditContext,
+ );
+ if (deleteCustomerAndUsersRes.success === false) {
+ res
+ .status(ErrorCodeMap[deleteCustomerAndUsersRes.code] ?? 500)
+ .json(deleteCustomerAndUsersRes);
+ return;
+ }
+
+ res.status(200).json({
+ success: true,
+ data: deleteCustomerAndUsersRes.data,
+ });
+ return;
+}
diff --git a/backend/admin_api/src/routes/delete_ks_node.ts b/backend/admin_api/src/routes/delete_ks_node.ts
new file mode 100644
index 000000000..3c13d0925
--- /dev/null
+++ b/backend/admin_api/src/routes/delete_ks_node.ts
@@ -0,0 +1,25 @@
+import type { Response } from "express";
+import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
+import type {
+ DeactivateKSNodeRequest,
+ DeactivateKSNodeResponse,
+} from "@oko-wallet/oko-types/admin";
+import { ErrorCodeMap } from "@oko-wallet/oko-api-error-codes";
+
+import { type AuthenticatedAdminRequest } from "@oko-wallet-admin-api/middleware";
+import { deleteKSNode } from "@oko-wallet-admin-api/api/ks_node";
+
+export async function delete_ks_node(
+ req: AuthenticatedAdminRequest,
+ res: Response>,
+) {
+ const state = req.app.locals;
+
+ const result = await deleteKSNode(state.db, req.body, req.auditContext);
+ if (!result.success) {
+ res.status(ErrorCodeMap[result.code] ?? 500).json(result);
+ return;
+ }
+
+ res.status(200).json(result);
+}
diff --git a/backend/admin_api/src/routes/ewallet_wallet.ts b/backend/admin_api/src/routes/ewallet_wallet.ts
deleted file mode 100644
index 96bcee887..000000000
--- a/backend/admin_api/src/routes/ewallet_wallet.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
-import type {
- GetWalletListRequest,
- GetWalletListResponse,
-} from "@oko-wallet-types/admin";
-import type { Response, Router } from "express";
-import { ErrorCodeMap } from "@oko-wallet/oko-api-error-codes";
-import { registry } from "@oko-wallet/oko-api-openapi";
-import {
- AdminAuthHeaderSchema,
- ErrorResponseSchema,
-} from "@oko-wallet/oko-api-openapi/common";
-import {
- GetWalletListRequestSchema,
- GetWalletListSuccessResponseSchema,
-} from "@oko-wallet/oko-api-openapi/oko_admin";
-
-import {
- adminAuthMiddleware,
- type AuthenticatedAdminRequest,
-} from "@oko-wallet-admin-api/middleware";
-import { getWalletList } from "@oko-wallet-admin-api/api/ewallet_wallet";
-
-export function setWalletRoutes(router: Router) {
- registry.registerPath({
- method: "post",
- path: "/oko_admin/v1/wallet/get_wallet_list",
- tags: ["Admin"],
- summary: "Get wallet list with pagination",
- description: "Retrieves a list of wallets with pagination",
- security: [{ adminAuth: [] }],
- request: {
- headers: AdminAuthHeaderSchema,
- body: {
- required: false,
- content: {
- "application/json": {
- schema: GetWalletListRequestSchema,
- },
- },
- },
- },
- responses: {
- 200: {
- description: "Wallet list retrieved successfully",
- content: {
- "application/json": {
- schema: GetWalletListSuccessResponseSchema,
- },
- },
- },
- 401: {
- description: "Unauthorized",
- content: {
- "application/json": {
- schema: ErrorResponseSchema,
- },
- },
- },
- 500: {
- description: "Server error",
- content: {
- "application/json": {
- schema: ErrorResponseSchema,
- },
- },
- },
- },
- });
- router.post(
- "/wallet/get_wallet_list",
- adminAuthMiddleware,
- async (
- req: AuthenticatedAdminRequest,
- res: Response>,
- ) => {
- const state = req.app.locals;
-
- const result = await getWalletList(state.db, req.body);
- if (!result.success) {
- res.status(ErrorCodeMap[result.code] ?? 500).json(result);
- return;
- }
-
- res.status(200).json(result);
- },
- );
-}
diff --git a/backend/admin_api/src/routes/get_all_ks_nodes.ts b/backend/admin_api/src/routes/get_all_ks_nodes.ts
new file mode 100644
index 000000000..f0e7190bc
--- /dev/null
+++ b/backend/admin_api/src/routes/get_all_ks_nodes.ts
@@ -0,0 +1,22 @@
+import type { Response } from "express";
+import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
+import type { GetAllKSNodeResponse } from "@oko-wallet/oko-types/admin";
+import { ErrorCodeMap } from "@oko-wallet/oko-api-error-codes";
+
+import { type AuthenticatedAdminRequest } from "@oko-wallet-admin-api/middleware";
+import { getAllKSNodes } from "@oko-wallet-admin-api/api/ks_node";
+
+export async function get_all_ks_nodes(
+ req: AuthenticatedAdminRequest,
+ res: Response>,
+) {
+ const state = req.app.locals;
+
+ const result = await getAllKSNodes(state.db);
+ if (!result.success) {
+ res.status(ErrorCodeMap[result.code] ?? 500).json(result);
+ return;
+ }
+
+ res.status(200).json(result);
+}
diff --git a/backend/admin_api/src/routes/get_audit_logs.ts b/backend/admin_api/src/routes/get_audit_logs.ts
new file mode 100644
index 000000000..b2fac3cbe
--- /dev/null
+++ b/backend/admin_api/src/routes/get_audit_logs.ts
@@ -0,0 +1,65 @@
+import { Pool } from "pg";
+import type { Response } from "express";
+import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
+import type { AuditEventFilter } from "@oko-wallet/oko-types/admin";
+import type { GetAuditLogsResponse } from "@oko-wallet/oko-types/admin";
+import { getAuditLogs } from "@oko-wallet-admin-api/api/audit";
+import { type AuthenticatedAdminRequest } from "@oko-wallet-admin-api/middleware";
+
+export async function get_audit_logs(
+ req: AuthenticatedAdminRequest,
+ res: Response>,
+) {
+ const state = req.app.locals;
+ const db = state.db as Pool;
+ try {
+ const params: AuditEventFilter = {};
+
+ if (req.query.target_type) {
+ params.target_type = req.query.target_type as string;
+ }
+ if (req.query.target_id) {
+ params.target_id = req.query.target_id as string;
+ }
+ if (req.query.action) {
+ params.action = req.query.action as string;
+ }
+ if (req.query.actor) {
+ params.actor = req.query.actor as string;
+ }
+ if (req.query.source) {
+ params.source = req.query.source as string;
+ }
+ if (req.query.outcome) {
+ params.outcome = req.query.outcome as "success" | "failure" | "denied";
+ }
+ if (req.query.occurred_after) {
+ params.occurred_after = new Date(req.query.occurred_after as string);
+ }
+ if (req.query.occurred_before) {
+ params.occurred_before = new Date(req.query.occurred_before as string);
+ }
+ if (req.query.limit) {
+ params.limit = parseInt(req.query.limit as string);
+ }
+ if (req.query.offset) {
+ params.offset = parseInt(req.query.offset as string);
+ }
+
+ const result = await getAuditLogs(db, params);
+
+ if (result.success) {
+ res.status(200).json(result);
+ } else {
+ res.status(500).json(result);
+ }
+ } catch (error) {
+ const errorResponse: OkoApiResponse = {
+ success: false,
+ code: "UNKNOWN_ERROR",
+ msg: `Failed to get audit logs: ${error instanceof Error ? error.message : String(error)}`,
+ };
+
+ res.status(500).json(errorResponse);
+ }
+}
diff --git a/backend/admin_api/src/routes/get_audit_logs_count.ts b/backend/admin_api/src/routes/get_audit_logs_count.ts
new file mode 100644
index 000000000..1e73615c2
--- /dev/null
+++ b/backend/admin_api/src/routes/get_audit_logs_count.ts
@@ -0,0 +1,59 @@
+import { Pool } from "pg";
+import type { Response } from "express";
+import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
+import type { AuditEventFilter } from "@oko-wallet/oko-types/admin";
+import type { GetAuditLogsCountResponse } from "@oko-wallet/oko-types/admin";
+import { getAuditLogsCount } from "@oko-wallet-admin-api/api/audit";
+import { type AuthenticatedAdminRequest } from "@oko-wallet-admin-api/middleware";
+
+export async function get_audit_logs_count(
+ req: AuthenticatedAdminRequest,
+ res: Response>,
+) {
+ const state = req.app.locals;
+ const db = state.db as Pool;
+ try {
+ const params: AuditEventFilter = {};
+
+ if (req.query.target_type) {
+ params.target_type = req.query.target_type as string;
+ }
+ if (req.query.target_id) {
+ params.target_id = req.query.target_id as string;
+ }
+ if (req.query.action) {
+ params.action = req.query.action as string;
+ }
+ if (req.query.actor) {
+ params.actor = req.query.actor as string;
+ }
+ if (req.query.source) {
+ params.source = req.query.source as string;
+ }
+ if (req.query.outcome) {
+ params.outcome = req.query.outcome as "success" | "failure" | "denied";
+ }
+ if (req.query.occurred_after) {
+ params.occurred_after = new Date(req.query.occurred_after as string);
+ }
+ if (req.query.occurred_before) {
+ params.occurred_before = new Date(req.query.occurred_before as string);
+ }
+
+ const result = await getAuditLogsCount(db, params);
+
+ if (result.success) {
+ res.status(200).json(result);
+ } else {
+ res.status(500).json(result);
+ }
+ } catch (error) {
+ const errorResponse: OkoApiResponse = {
+ success: false,
+ code: "UNKNOWN_ERROR",
+ msg: `Failed to get audit logs count: ${error instanceof Error ? error.message : String(error)}`,
+ };
+
+ res.status(500).json(errorResponse);
+ }
+}
diff --git a/backend/admin_api/src/routes/get_customer.ts b/backend/admin_api/src/routes/get_customer.ts
new file mode 100644
index 000000000..8587af0f3
--- /dev/null
+++ b/backend/admin_api/src/routes/get_customer.ts
@@ -0,0 +1,35 @@
+import type { Response } from "express";
+import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
+import type { Customer } from "@oko-wallet/oko-types/customers";
+import { ErrorCodeMap } from "@oko-wallet/oko-api-error-codes";
+
+import { type AuthenticatedAdminRequest } from "@oko-wallet-admin-api/middleware";
+import { getCustomerById } from "@oko-wallet-admin-api/api/customer";
+
+export async function get_customer(
+ req: AuthenticatedAdminRequest,
+ res: Response>,
+) {
+ const state = req.app.locals;
+ const { customer_id: customerId } = req.params;
+
+ if (!customerId) {
+ res.status(400).json({
+ success: false,
+ code: "MISSING_CUSTOMER_ID",
+ msg: "Customer ID is required",
+ });
+ return;
+ }
+
+ const getCustomerByIdRes = await getCustomerById(state.db, customerId);
+ if (getCustomerByIdRes.success === false) {
+ res
+ .status(ErrorCodeMap[getCustomerByIdRes.code] ?? 500)
+ .json(getCustomerByIdRes);
+ return;
+ }
+
+ res.status(200).json({ success: true, data: getCustomerByIdRes.data });
+ return;
+}
diff --git a/backend/admin_api/src/routes/get_customer_list.ts b/backend/admin_api/src/routes/get_customer_list.ts
new file mode 100644
index 000000000..ca3d9a340
--- /dev/null
+++ b/backend/admin_api/src/routes/get_customer_list.ts
@@ -0,0 +1,46 @@
+import type { Response } from "express";
+import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
+import type { CustomerWithAPIKeys } from "@oko-wallet/oko-types/customers";
+import { ErrorCodeMap } from "@oko-wallet/oko-api-error-codes";
+
+import { type AuthenticatedAdminRequest } from "@oko-wallet-admin-api/middleware";
+import { getCustomerList } from "@oko-wallet-admin-api/api/customer";
+
+export async function get_customer_list(
+ req: AuthenticatedAdminRequest,
+ res: Response<
+ OkoApiResponse<{
+ customerWithAPIKeysList: CustomerWithAPIKeys[];
+ pagination: {
+ total: number;
+ current_page: number;
+ total_pages: number;
+ };
+ }>
+ >,
+) {
+ const state = req.app.locals;
+
+ let { limit, offset } = req.query;
+ if (!limit || !offset) {
+ limit = 10;
+ offset = 0;
+ } else {
+ limit = parseInt(limit);
+ offset = parseInt(offset);
+ }
+
+ const getCustomerListRes = await getCustomerList(state.db, limit, offset);
+ if (getCustomerListRes.success === false) {
+ res
+ .status(ErrorCodeMap[getCustomerListRes.code] ?? 500)
+ .json(getCustomerListRes);
+ return;
+ }
+
+ res.status(200).json({
+ success: true,
+ data: getCustomerListRes.data,
+ });
+ return;
+}
diff --git a/backend/admin_api/src/routes/get_ks_node_by_id.ts b/backend/admin_api/src/routes/get_ks_node_by_id.ts
new file mode 100644
index 000000000..257942677
--- /dev/null
+++ b/backend/admin_api/src/routes/get_ks_node_by_id.ts
@@ -0,0 +1,24 @@
+import type { Response } from "express";
+import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
+import type {
+ GetKSNodeByIdRequest,
+ GetKSNodeByIdResponse,
+} from "@oko-wallet/oko-types/admin";
+import { ErrorCodeMap } from "@oko-wallet/oko-api-error-codes";
+
+import { type AuthenticatedAdminRequest } from "@oko-wallet-admin-api/middleware";
+import { getKSNodeById } from "@oko-wallet-admin-api/api/ks_node";
+
+export async function get_ks_node_by_id(
+ req: AuthenticatedAdminRequest,
+ res: Response>,
+) {
+ const state = req.app.locals;
+ const result = await getKSNodeById(state.db, req.body);
+ if (!result.success) {
+ res.status(ErrorCodeMap[result.code] ?? 500).json(result);
+ return;
+ }
+
+ res.status(200).json(result);
+}
diff --git a/backend/admin_api/src/routes/get_tss_all_activation_setting.ts b/backend/admin_api/src/routes/get_tss_all_activation_setting.ts
new file mode 100644
index 000000000..52e11dc62
--- /dev/null
+++ b/backend/admin_api/src/routes/get_tss_all_activation_setting.ts
@@ -0,0 +1,22 @@
+import type { Response } from "express";
+import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
+import type { GetTssAllActivationSettingResponse } from "@oko-wallet-types/admin";
+import { ErrorCodeMap } from "@oko-wallet/oko-api-error-codes";
+
+import { type AuthenticatedAdminRequest } from "@oko-wallet-admin-api/middleware";
+import { getTssAllActivationSetting } from "@oko-wallet-admin-api/api/tss";
+
+export async function get_tss_all_activation_setting(
+ req: AuthenticatedAdminRequest,
+ res: Response>,
+) {
+ const state = req.app.locals as any;
+
+ const result = await getTssAllActivationSetting(state.db);
+ if (!result.success) {
+ res.status(ErrorCodeMap[result.code] ?? 500).json(result);
+ return;
+ }
+
+ res.status(200).json(result);
+}
diff --git a/backend/admin_api/src/routes/get_tss_session_list.ts b/backend/admin_api/src/routes/get_tss_session_list.ts
new file mode 100644
index 000000000..c89675dc2
--- /dev/null
+++ b/backend/admin_api/src/routes/get_tss_session_list.ts
@@ -0,0 +1,25 @@
+import type { Response } from "express";
+import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
+import type {
+ GetTssSessionListRequest,
+ GetTssSessionListResponse,
+} from "@oko-wallet-types/admin";
+import { ErrorCodeMap } from "@oko-wallet/oko-api-error-codes";
+
+import { type AuthenticatedAdminRequest } from "@oko-wallet-admin-api/middleware";
+import { getTssSessionList } from "@oko-wallet-admin-api/api/tss";
+
+export async function get_tss_session_list(
+ req: AuthenticatedAdminRequest,
+ res: Response>,
+) {
+ const state = req.app.locals as any;
+
+ const result = await getTssSessionList(state.db, req.body);
+ if (!result.success) {
+ res.status(ErrorCodeMap[result.code] ?? 500).json(result);
+ return;
+ }
+
+ res.status(200).json(result);
+}
diff --git a/backend/admin_api/src/routes/get_wallet_list.ts b/backend/admin_api/src/routes/get_wallet_list.ts
new file mode 100644
index 000000000..fa0f2d39a
--- /dev/null
+++ b/backend/admin_api/src/routes/get_wallet_list.ts
@@ -0,0 +1,25 @@
+import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
+import type {
+ GetWalletListRequest,
+ GetWalletListResponse,
+} from "@oko-wallet-types/admin";
+import type { Response } from "express";
+import { ErrorCodeMap } from "@oko-wallet/oko-api-error-codes";
+
+import { type AuthenticatedAdminRequest } from "@oko-wallet-admin-api/middleware";
+import { getWalletList } from "@oko-wallet-admin-api/api/wallet";
+
+export async function get_wallet_list(
+ req: AuthenticatedAdminRequest,
+ res: Response>,
+) {
+ const state = req.app.locals;
+
+ const result = await getWalletList(state.db, req.body);
+ if (!result.success) {
+ res.status(ErrorCodeMap[result.code] ?? 500).json(result);
+ return;
+ }
+
+ res.status(200).json(result);
+}
diff --git a/backend/admin_api/src/routes/index.ts b/backend/admin_api/src/routes/index.ts
index 8ac93eee1..68e3b5471 100644
--- a/backend/admin_api/src/routes/index.ts
+++ b/backend/admin_api/src/routes/index.ts
@@ -1,19 +1,727 @@
import express from "express";
+import multer from "multer";
+import {
+ GetWalletListRequestSchema,
+ GetWalletListSuccessResponseSchema,
+} from "@oko-wallet/oko-api-openapi/oko_admin";
+import { registry } from "@oko-wallet/oko-api-openapi";
+import {
+ ErrorResponseSchema,
+ AdminAuthHeaderSchema,
+} from "@oko-wallet/oko-api-openapi/common";
+import {
+ GetTssSessionListSuccessResponseSchema,
+ GetTssAllActivationSettingSuccessResponseSchema,
+ SetTssAllActivationSettingRequestSchema,
+ SetTssAllActivationSettingSuccessResponseSchema,
+} from "@oko-wallet/oko-api-openapi/oko_admin";
+import {
+ CreateCustomerWithDashboardUserRequestSchema,
+ CreateCustomerSuccessResponseSchema,
+ GetCustomerListQuerySchema,
+ GetCustomerListSuccessResponseSchema,
+ CustomerIdParamSchema,
+ GetCustomerSuccessResponseSchema,
+ DeleteCustomerSuccessResponseSchema,
+ GetTssSessionListRequestSchema,
+ GetAuditLogsQuerySchema,
+ GetAuditLogsCountQuerySchema,
+} from "@oko-wallet/oko-api-openapi/oko_admin";
+import {
+ LoginRequestSchema,
+ AdminLoginSuccessResponseSchema,
+ AdminLogoutSuccessResponseSchema,
+} from "@oko-wallet/oko-api-openapi/oko_admin";
-import { setCustomerRoutes } from "@oko-wallet-admin-api/routes/customer";
-import { setWalletRoutes } from "@oko-wallet-admin-api/routes/ewallet_wallet";
-import { setUserRoutes } from "@oko-wallet-admin-api/routes/user";
-import { setTssRoutes } from "@oko-wallet-admin-api/routes/tss";
-import { setKSNodeRoutes } from "@oko-wallet-admin-api/routes/ks_node";
+import { adminAuthMiddleware } from "@oko-wallet-admin-api/middleware";
+import { create_customer } from "./create_customer";
+import { get_customer_list } from "./get_customer_list";
+import { get_customer } from "./get_customer";
+import { delete_customer } from "./delete_customer";
+import { user_login } from "./user_login";
+import { user_logout } from "./user_logout";
+import { get_tss_session_list } from "./get_tss_session_list";
+import { get_tss_all_activation_setting } from "./get_tss_all_activation_setting";
+import { set_tss_all_activation_setting } from "./set_tss_all_activation_setting";
+import { get_wallet_list } from "./get_wallet_list";
+import { get_all_ks_nodes } from "./get_all_ks_nodes";
+import { get_ks_node_by_id } from "./get_ks_node_by_id";
+import { create_ks_node } from "./create_ks_node";
+import { deactivate_ks_node } from "./deactivate_ks_node";
+import { delete_ks_node } from "./delete_ks_node";
+import { update_ks_node } from "./update_ks_node";
+import { activate_ks_node } from "./activate_ks_node";
+import { get_audit_logs } from "./get_audit_logs";
+import { get_audit_logs_count } from "./get_audit_logs_count";
export function makeEWalletAdminRouter() {
const router = express.Router();
+ const upload = multer();
- setCustomerRoutes(router);
- setUserRoutes(router);
- setTssRoutes(router);
- setWalletRoutes(router);
- setKSNodeRoutes(router);
+ registry.registerPath({
+ method: "post",
+ path: "/oko_admin/v1/customer/create_customer",
+ tags: ["Admin"],
+ summary: "Create new customer",
+ description: "Creates a new customer with dashboard user account",
+ security: [{ adminAuth: [] }],
+ request: {
+ headers: AdminAuthHeaderSchema,
+ body: {
+ content: {
+ "multipart/form-data": {
+ schema: CreateCustomerWithDashboardUserRequestSchema,
+ },
+ },
+ },
+ },
+ responses: {
+ 201: {
+ description: "Customer created successfully",
+ content: {
+ "application/json": {
+ schema: CreateCustomerSuccessResponseSchema,
+ },
+ },
+ },
+ 400: {
+ description: "Invalid request",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ 401: {
+ description: "Unauthorized",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ 500: {
+ description: "Server error",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ },
+ });
+ router.post(
+ "/customer/create_customer",
+ adminAuthMiddleware,
+ upload.single("logo"),
+ create_customer,
+ );
+
+ registry.registerPath({
+ method: "get",
+ path: "/oko_admin/v1/customer/get_customer_list",
+ tags: ["Admin"],
+ summary: "Get customers with pagination",
+ description: "Retrieves a list of customers with pagination",
+ security: [{ adminAuth: [] }],
+ request: {
+ headers: AdminAuthHeaderSchema,
+ query: GetCustomerListQuerySchema,
+ },
+ responses: {
+ 200: {
+ description: "Customer list retrieved successfully",
+ content: {
+ "application/json": {
+ schema: GetCustomerListSuccessResponseSchema,
+ },
+ },
+ },
+ 401: {
+ description: "Unauthorized",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ 500: {
+ description: "Server error",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ },
+ });
+ router.get(
+ "/customer/get_customer_list",
+ adminAuthMiddleware,
+ get_customer_list,
+ );
+
+ registry.registerPath({
+ method: "get",
+ path: "/oko_admin/v1/customer/get_customer/{customer_id}",
+ tags: ["Admin"],
+ summary: "Get customer by ID",
+ description: "Retrieves customer information by customer ID",
+ security: [{ adminAuth: [] }],
+ request: {
+ headers: AdminAuthHeaderSchema,
+ params: CustomerIdParamSchema,
+ },
+ responses: {
+ 200: {
+ description: "Customer information retrieved successfully",
+ content: {
+ "application/json": {
+ schema: GetCustomerSuccessResponseSchema,
+ },
+ },
+ },
+ 400: {
+ description: "Invalid request",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ 401: {
+ description: "Unauthorized",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ 404: {
+ description: "Customer not found",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ 500: {
+ description: "Server error",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ },
+ });
+ router.get(
+ "/customer/get_customer/:customer_id",
+ adminAuthMiddleware,
+ get_customer,
+ );
+
+ registry.registerPath({
+ method: "post",
+ path: "/oko_admin/v1/customer/delete_customer/{customer_id}",
+ tags: ["Admin"],
+ summary: "Delete customer by ID",
+ description: "Deletes a customer by customer ID",
+ security: [{ adminAuth: [] }],
+ request: {
+ headers: AdminAuthHeaderSchema,
+ params: CustomerIdParamSchema,
+ },
+ responses: {
+ 200: {
+ description: "Customer deleted successfully",
+ content: {
+ "application/json": {
+ schema: DeleteCustomerSuccessResponseSchema,
+ },
+ },
+ },
+ 400: {
+ description: "Invalid request",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ 401: {
+ description: "Unauthorized",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ 500: {
+ description: "Server error",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ },
+ });
+ router.post(
+ "/customer/delete_customer/:customer_id",
+ adminAuthMiddleware,
+ delete_customer,
+ );
+
+ registry.registerPath({
+ method: "post",
+ path: "/oko_admin/v1/user/login",
+ tags: ["Admin"],
+ summary: "Admin login",
+ description: "Authenticates an admin user",
+ security: [],
+ request: {
+ body: {
+ required: true,
+ content: {
+ "application/json": {
+ schema: LoginRequestSchema,
+ },
+ },
+ },
+ },
+ responses: {
+ 200: {
+ description: "Successfully logged in",
+ content: {
+ "application/json": {
+ schema: AdminLoginSuccessResponseSchema,
+ },
+ },
+ },
+ 400: {
+ description: "Invalid request",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ 401: {
+ description: "Invalid credentials",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ 500: {
+ description: "Server error",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ },
+ });
+ router.post("/user/login", user_login);
+
+ registry.registerPath({
+ method: "post",
+ path: "/oko_admin/v1/user/logout",
+ tags: ["Admin"],
+ summary: "Admin logout",
+ description: "Logs out an admin user",
+ security: [{ adminAuth: [] }],
+ request: {
+ headers: AdminAuthHeaderSchema,
+ },
+ responses: {
+ 200: {
+ description: "Successfully logged out",
+ content: {
+ "application/json": {
+ schema: AdminLogoutSuccessResponseSchema,
+ },
+ },
+ },
+ 401: {
+ description: "Unauthorized",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ 500: {
+ description: "Server error",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ },
+ });
+ router.post("/user/logout", adminAuthMiddleware, user_logout);
+
+ registry.registerPath({
+ method: "post",
+ path: "/oko_admin/v1/tss/get_tss_session_list",
+ tags: ["Admin"],
+ summary: "Get tss sessions with pagination",
+ description: "Retrieves a list of TSS sessions with next/prev pagination",
+ security: [{ adminAuth: [] }],
+ request: {
+ headers: AdminAuthHeaderSchema,
+ body: {
+ required: false,
+ content: {
+ "application/json": {
+ schema: GetTssSessionListRequestSchema,
+ },
+ },
+ },
+ },
+ responses: {
+ 200: {
+ description: "TSS sessions retrieved successfully",
+ content: {
+ "application/json": {
+ schema: GetTssSessionListSuccessResponseSchema,
+ },
+ },
+ },
+ 401: {
+ description: "Unauthorized",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ 500: {
+ description: "Server error",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ },
+ });
+ router.post(
+ "/tss/get_tss_session_list",
+ adminAuthMiddleware,
+ get_tss_session_list,
+ );
+
+ registry.registerPath({
+ method: "post",
+ path: "/oko_admin/v1/tss/get_tss_all_activation_setting",
+ tags: ["Admin"],
+ summary: "Get TSS activation setting",
+ description: "Retrieves the current TSS activation setting",
+ security: [{ adminAuth: [] }],
+ request: {
+ headers: AdminAuthHeaderSchema,
+ },
+ responses: {
+ 200: {
+ description: "TSS activation setting retrieved successfully",
+ content: {
+ "application/json": {
+ schema: GetTssAllActivationSettingSuccessResponseSchema,
+ },
+ },
+ },
+ 401: {
+ description: "Unauthorized",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ 500: {
+ description: "Server error",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ },
+ });
+
+ router.post(
+ "/tss/get_tss_all_activation_setting",
+ adminAuthMiddleware,
+ get_tss_all_activation_setting,
+ );
+
+ registry.registerPath({
+ method: "post",
+ path: "/oko_admin/v1/tss/set_tss_all_activation_setting",
+ tags: ["Admin"],
+ summary: "Set TSS activation setting",
+ description: "Enable or disable TSS functionality for all users",
+ security: [{ adminAuth: [] }],
+ request: {
+ headers: AdminAuthHeaderSchema,
+ body: {
+ required: true,
+ content: {
+ "application/json": {
+ schema: SetTssAllActivationSettingRequestSchema,
+ },
+ },
+ },
+ },
+ responses: {
+ 200: {
+ description: "TSS activation setting updated successfully",
+ content: {
+ "application/json": {
+ schema: SetTssAllActivationSettingSuccessResponseSchema,
+ },
+ },
+ },
+ 400: {
+ description: "Invalid request",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ 401: {
+ description: "Unauthorized",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ 500: {
+ description: "Server error",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ },
+ });
+ router.post(
+ "/tss/set_tss_all_activation_setting",
+ adminAuthMiddleware,
+ set_tss_all_activation_setting,
+ );
+
+ registry.registerPath({
+ method: "post",
+ path: "/oko_admin/v1/wallet/get_wallet_list",
+ tags: ["Admin"],
+ summary: "Get wallet list with pagination",
+ description: "Retrieves a list of wallets with pagination",
+ security: [{ adminAuth: [] }],
+ request: {
+ headers: AdminAuthHeaderSchema,
+ body: {
+ required: false,
+ content: {
+ "application/json": {
+ schema: GetWalletListRequestSchema,
+ },
+ },
+ },
+ },
+ responses: {
+ 200: {
+ description: "Wallet list retrieved successfully",
+ content: {
+ "application/json": {
+ schema: GetWalletListSuccessResponseSchema,
+ },
+ },
+ },
+ 401: {
+ description: "Unauthorized",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ 500: {
+ description: "Server error",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ },
+ });
+ router.post("/wallet/get_wallet_list", adminAuthMiddleware, get_wallet_list);
+
+ router.post(
+ "/ks_node/get_all_ks_nodes",
+ adminAuthMiddleware,
+ get_all_ks_nodes,
+ );
+
+ router.post(
+ "/ks_node/get_ks_node_by_id",
+ adminAuthMiddleware,
+ get_ks_node_by_id,
+ );
+
+ router.post("/ks_node/create_ks_node", adminAuthMiddleware, create_ks_node);
+
+ router.post(
+ "/ks_node/deactivate_ks_node",
+ adminAuthMiddleware,
+ deactivate_ks_node,
+ );
+
+ router.post("/ks_node/delete_ks_node", adminAuthMiddleware, delete_ks_node);
+
+ router.post("/ks_node/update_ks_node", adminAuthMiddleware, update_ks_node);
+
+ router.post(
+ "/ks_node/activate_ks_node",
+ adminAuthMiddleware,
+ activate_ks_node,
+ );
+
+ registry.registerPath({
+ method: "get",
+ path: "/oko_admin/v1/audit/logs",
+ tags: ["Admin"],
+ summary: "Get audit logs",
+ description: "Retrieves audit logs with filtering and pagination",
+ security: [{ adminAuth: [] }],
+ request: {
+ headers: AdminAuthHeaderSchema,
+ query: GetAuditLogsQuerySchema,
+ },
+ responses: {
+ 200: {
+ description: "Audit logs retrieved successfully",
+ content: {
+ "application/json": {
+ schema: {
+ type: "object",
+ properties: {
+ success: { type: "boolean" },
+ data: {
+ type: "array",
+ items: {
+ type: "object",
+ properties: {
+ id: { type: "string" },
+ occurred_at: { type: "string", format: "date-time" },
+ request_id: { type: "string" },
+ actor: { type: "string" },
+ actor_ip: { type: "string" },
+ user_agent: { type: "string" },
+ source: { type: "string" },
+ action: { type: "string" },
+ target_type: { type: "string" },
+ target_id: { type: "string" },
+ changes: {
+ type: "array",
+ items: {
+ type: "object",
+ properties: {
+ field: { type: "string" },
+ from: {},
+ to: {},
+ },
+ },
+ },
+ params: { type: "object" },
+ outcome: {
+ type: "string",
+ enum: ["success", "failure", "denied"],
+ },
+ error: { type: "string" },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ 401: {
+ description: "Unauthorized",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ 500: {
+ description: "Server error",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ },
+ });
+ router.get("/audit/logs", adminAuthMiddleware, get_audit_logs);
+
+ registry.registerPath({
+ method: "get",
+ path: "/oko_admin/v1/audit/logs/count",
+ tags: ["Admin"],
+ summary: "Get audit logs count",
+ description: "Retrieves count of audit logs matching filters",
+ security: [{ adminAuth: [] }],
+ request: {
+ headers: AdminAuthHeaderSchema,
+ query: GetAuditLogsCountQuerySchema,
+ },
+ responses: {
+ 200: {
+ description: "Audit logs count retrieved successfully",
+ content: {
+ "application/json": {
+ schema: {
+ type: "object",
+ properties: {
+ success: { type: "boolean" },
+ data: { type: "integer" },
+ },
+ },
+ },
+ },
+ },
+ 401: {
+ description: "Unauthorized",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ 500: {
+ description: "Server error",
+ content: {
+ "application/json": {
+ schema: ErrorResponseSchema,
+ },
+ },
+ },
+ },
+ });
+ router.get("/audit/logs/count", adminAuthMiddleware, get_audit_logs_count);
return router;
}
diff --git a/backend/admin_api/src/routes/ks_node.ts b/backend/admin_api/src/routes/ks_node.ts
deleted file mode 100644
index c4fa6ead8..000000000
--- a/backend/admin_api/src/routes/ks_node.ts
+++ /dev/null
@@ -1,164 +0,0 @@
-import type { Response, Router } from "express";
-import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
-import type {
- GetAllKSNodeResponse,
- CreateKSNodeResponse,
- DeactivateKSNodeRequest,
- DeactivateKSNodeResponse,
- GetKSNodeByIdRequest,
- GetKSNodeByIdResponse,
- UpdateKSNodeRequest,
- UpdateKSNodeResponse,
- ActivateKSNodeRequest,
- ActivateKSNodeResponse,
-} from "@oko-wallet/oko-types/admin";
-import type { CreateKSNodeRequest } from "@oko-wallet/oko-types/admin";
-import { ErrorCodeMap } from "@oko-wallet/oko-api-error-codes";
-
-import {
- adminAuthMiddleware,
- type AuthenticatedAdminRequest,
-} from "@oko-wallet-admin-api/middleware";
-import {
- getAllKSNodes,
- createKSNode,
- deactivateKSNode,
- getKSNodeById,
- updateKSNode,
- activateKSNode,
- deleteKSNode,
-} from "@oko-wallet-admin-api/api/ks_node";
-
-export function setKSNodeRoutes(router: Router) {
- router.post(
- "/ks_node/get_all_ks_nodes",
- adminAuthMiddleware,
- async (
- req: AuthenticatedAdminRequest,
- res: Response>,
- ) => {
- const state = req.app.locals;
-
- const result = await getAllKSNodes(state.db);
- if (!result.success) {
- res.status(ErrorCodeMap[result.code] ?? 500).json(result);
- return;
- }
-
- res.status(200).json(result);
- },
- );
-
- router.post(
- "/ks_node/get_ks_node_by_id",
- adminAuthMiddleware,
- async (
- req: AuthenticatedAdminRequest,
- res: Response>,
- ) => {
- const state = req.app.locals;
- const result = await getKSNodeById(state.db, req.body);
- if (!result.success) {
- res.status(ErrorCodeMap[result.code] ?? 500).json(result);
- return;
- }
-
- res.status(200).json(result);
- },
- );
-
- router.post(
- "/ks_node/create_ks_node",
- adminAuthMiddleware,
- async (
- req: AuthenticatedAdminRequest,
- res: Response>,
- ) => {
- const state = req.app.locals;
-
- const result = await createKSNode(state.db, req.body);
- if (!result.success) {
- res.status(ErrorCodeMap[result.code] ?? 500).json(result);
- return;
- }
-
- res.status(200).json(result);
- },
- );
-
- router.post(
- "/ks_node/deactivate_ks_node",
- adminAuthMiddleware,
- async (
- req: AuthenticatedAdminRequest,
- res: Response>,
- ) => {
- const state = req.app.locals;
-
- const result = await deactivateKSNode(state.db, req.body);
- if (!result.success) {
- res.status(ErrorCodeMap[result.code] ?? 500).json(result);
- return;
- }
-
- res.status(200).json(result);
- },
- );
-
- router.post(
- "/ks_node/delete_ks_node",
- adminAuthMiddleware,
- async (
- req: AuthenticatedAdminRequest,
- res: Response>,
- ) => {
- const state = req.app.locals;
-
- const result = await deleteKSNode(state.db, req.body);
- if (!result.success) {
- res.status(ErrorCodeMap[result.code] ?? 500).json(result);
- return;
- }
-
- res.status(200).json(result);
- },
- );
-
- router.post(
- "/ks_node/update_ks_node",
- adminAuthMiddleware,
- async (
- req: AuthenticatedAdminRequest,
- res: Response>,
- ) => {
- const state = req.app.locals;
-
- const result = await updateKSNode(state.db, req.body);
- if (!result.success) {
- res.status(ErrorCodeMap[result.code] ?? 500).json(result);
- return;
- }
-
- res.status(200).json(result);
- },
- );
-
- router.post(
- "/ks_node/activate_ks_node",
- adminAuthMiddleware,
- async (
- req: AuthenticatedAdminRequest,
- res: Response>,
- ) => {
- const state = req.app.locals;
-
- const result = await activateKSNode(state.db, req.body);
- if (!result.success) {
- res.status(ErrorCodeMap[result.code] ?? 500).json(result);
- return;
- }
-
- res.status(200).json(result);
- },
- );
-}
diff --git a/backend/admin_api/src/routes/set_tss_all_activation_setting.ts b/backend/admin_api/src/routes/set_tss_all_activation_setting.ts
new file mode 100644
index 000000000..b6efe038c
--- /dev/null
+++ b/backend/admin_api/src/routes/set_tss_all_activation_setting.ts
@@ -0,0 +1,29 @@
+import type { Response } from "express";
+import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
+import type {
+ SetTssAllActivationSettingRequest,
+ SetTssAllActivationSettingResponse,
+} from "@oko-wallet-types/admin";
+import { ErrorCodeMap } from "@oko-wallet/oko-api-error-codes";
+
+import { type AuthenticatedAdminRequest } from "@oko-wallet-admin-api/middleware";
+import { setTssAllActivationSetting } from "@oko-wallet-admin-api/api/tss";
+
+export async function set_tss_all_activation_setting(
+ req: AuthenticatedAdminRequest,
+ res: Response>,
+) {
+ const state = req.app.locals as any;
+
+ const result = await setTssAllActivationSetting(
+ state.db,
+ req.body,
+ req.auditContext,
+ );
+ if (!result.success) {
+ res.status(ErrorCodeMap[result.code] ?? 500).json(result);
+ return;
+ }
+
+ res.status(200).json(result);
+}
diff --git a/backend/admin_api/src/routes/tss.ts b/backend/admin_api/src/routes/tss.ts
deleted file mode 100644
index 6d00e06f8..000000000
--- a/backend/admin_api/src/routes/tss.ts
+++ /dev/null
@@ -1,228 +0,0 @@
-import type { Router, Response } from "express";
-import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
-import type {
- GetTssSessionListRequest,
- GetTssSessionListResponse,
- GetTssAllActivationSettingResponse,
- SetTssAllActivationSettingRequest,
- SetTssAllActivationSettingResponse,
-} from "@oko-wallet-types/admin";
-import { ErrorCodeMap } from "@oko-wallet/oko-api-error-codes";
-import { registry } from "@oko-wallet/oko-api-openapi";
-import {
- AdminAuthHeaderSchema,
- ErrorResponseSchema,
-} from "@oko-wallet/oko-api-openapi/common";
-import {
- GetTssSessionListRequestSchema,
- GetTssSessionListSuccessResponseSchema,
- GetTssAllActivationSettingSuccessResponseSchema,
- SetTssAllActivationSettingRequestSchema,
- SetTssAllActivationSettingSuccessResponseSchema,
-} from "@oko-wallet/oko-api-openapi/oko_admin";
-
-import {
- adminAuthMiddleware,
- type AuthenticatedAdminRequest,
-} from "@oko-wallet-admin-api/middleware";
-import {
- getTssSessionList,
- getTssAllActivationSetting,
- setTssAllActivationSetting,
-} from "@oko-wallet-admin-api/api/tss";
-
-export function setTssRoutes(router: Router) {
- registry.registerPath({
- method: "post",
- path: "/oko_admin/v1/tss/get_tss_session_list",
- tags: ["Admin"],
- summary: "Get tss sessions with pagination",
- description: "Retrieves a list of TSS sessions with next/prev pagination",
- security: [{ adminAuth: [] }],
- request: {
- headers: AdminAuthHeaderSchema,
- body: {
- required: false,
- content: {
- "application/json": {
- schema: GetTssSessionListRequestSchema,
- },
- },
- },
- },
- responses: {
- 200: {
- description: "TSS sessions retrieved successfully",
- content: {
- "application/json": {
- schema: GetTssSessionListSuccessResponseSchema,
- },
- },
- },
- 401: {
- description: "Unauthorized",
- content: {
- "application/json": {
- schema: ErrorResponseSchema,
- },
- },
- },
- 500: {
- description: "Server error",
- content: {
- "application/json": {
- schema: ErrorResponseSchema,
- },
- },
- },
- },
- });
-
- router.post(
- "/tss/get_tss_session_list",
- adminAuthMiddleware,
- async (
- req: AuthenticatedAdminRequest,
- res: Response>,
- ) => {
- const state = req.app.locals as any;
-
- const result = await getTssSessionList(state.db, req.body);
- if (!result.success) {
- res.status(ErrorCodeMap[result.code] ?? 500).json(result);
- return;
- }
-
- res.status(200).json(result);
- },
- );
-
- registry.registerPath({
- method: "post",
- path: "/oko_admin/v1/tss/get_tss_all_activation_setting",
- tags: ["Admin"],
- summary: "Get TSS activation setting",
- description: "Retrieves the current TSS activation setting",
- security: [{ adminAuth: [] }],
- request: {
- headers: AdminAuthHeaderSchema,
- },
- responses: {
- 200: {
- description: "TSS activation setting retrieved successfully",
- content: {
- "application/json": {
- schema: GetTssAllActivationSettingSuccessResponseSchema,
- },
- },
- },
- 401: {
- description: "Unauthorized",
- content: {
- "application/json": {
- schema: ErrorResponseSchema,
- },
- },
- },
- 500: {
- description: "Server error",
- content: {
- "application/json": {
- schema: ErrorResponseSchema,
- },
- },
- },
- },
- });
-
- router.post(
- "/tss/get_tss_all_activation_setting",
- adminAuthMiddleware,
- async (
- req: AuthenticatedAdminRequest,
- res: Response>,
- ) => {
- const state = req.app.locals as any;
-
- const result = await getTssAllActivationSetting(state.db);
- if (!result.success) {
- res.status(ErrorCodeMap[result.code] ?? 500).json(result);
- return;
- }
-
- res.status(200).json(result);
- },
- );
-
- registry.registerPath({
- method: "post",
- path: "/oko_admin/v1/tss/set_tss_all_activation_setting",
- tags: ["Admin"],
- summary: "Set TSS activation setting",
- description: "Enable or disable TSS functionality for all users",
- security: [{ adminAuth: [] }],
- request: {
- headers: AdminAuthHeaderSchema,
- body: {
- required: true,
- content: {
- "application/json": {
- schema: SetTssAllActivationSettingRequestSchema,
- },
- },
- },
- },
- responses: {
- 200: {
- description: "TSS activation setting updated successfully",
- content: {
- "application/json": {
- schema: SetTssAllActivationSettingSuccessResponseSchema,
- },
- },
- },
- 400: {
- description: "Invalid request",
- content: {
- "application/json": {
- schema: ErrorResponseSchema,
- },
- },
- },
- 401: {
- description: "Unauthorized",
- content: {
- "application/json": {
- schema: ErrorResponseSchema,
- },
- },
- },
- 500: {
- description: "Server error",
- content: {
- "application/json": {
- schema: ErrorResponseSchema,
- },
- },
- },
- },
- });
- router.post(
- "/tss/set_tss_all_activation_setting",
- adminAuthMiddleware,
- async (
- req: AuthenticatedAdminRequest,
- res: Response>,
- ) => {
- const state = req.app.locals as any;
-
- const result = await setTssAllActivationSetting(state.db, req.body);
- if (!result.success) {
- res.status(ErrorCodeMap[result.code] ?? 500).json(result);
- return;
- }
-
- res.status(200).json(result);
- },
- );
-}
diff --git a/backend/admin_api/src/routes/update_ks_node.ts b/backend/admin_api/src/routes/update_ks_node.ts
new file mode 100644
index 000000000..304d29635
--- /dev/null
+++ b/backend/admin_api/src/routes/update_ks_node.ts
@@ -0,0 +1,25 @@
+import type { Response } from "express";
+import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
+import type {
+ UpdateKSNodeRequest,
+ UpdateKSNodeResponse,
+} from "@oko-wallet/oko-types/admin";
+import { ErrorCodeMap } from "@oko-wallet/oko-api-error-codes";
+
+import { type AuthenticatedAdminRequest } from "@oko-wallet-admin-api/middleware";
+import { updateKSNode } from "@oko-wallet-admin-api/api/ks_node";
+
+export async function update_ks_node(
+ req: AuthenticatedAdminRequest,
+ res: Response>,
+) {
+ const state = req.app.locals;
+
+ const result = await updateKSNode(state.db, req.body, req.auditContext);
+ if (!result.success) {
+ res.status(ErrorCodeMap[result.code] ?? 500).json(result);
+ return;
+ }
+
+ res.status(200).json(result);
+}
diff --git a/backend/admin_api/src/routes/user.ts b/backend/admin_api/src/routes/user.ts
deleted file mode 100644
index aa2a2aa6a..000000000
--- a/backend/admin_api/src/routes/user.ts
+++ /dev/null
@@ -1,158 +0,0 @@
-import type { Router, Response, Request } from "express";
-import type {
- AdminLoginRequest,
- AdminLoginResponse,
- AdminLogoutResponse,
-} from "@oko-wallet/oko-types/admin";
-import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
-import { ErrorCodeMap } from "@oko-wallet/oko-api-error-codes";
-import { registry } from "@oko-wallet/oko-api-openapi";
-import {
- ErrorResponseSchema,
- AdminAuthHeaderSchema,
-} from "@oko-wallet/oko-api-openapi/common";
-import {
- LoginRequestSchema,
- AdminLoginSuccessResponseSchema,
- AdminLogoutSuccessResponseSchema,
-} from "@oko-wallet/oko-api-openapi/oko_admin";
-
-import { adminAuthMiddleware } from "@oko-wallet-admin-api/middleware";
-import { login, logout } from "@oko-wallet-admin-api/api/user";
-
-export function setUserRoutes(router: Router) {
- registry.registerPath({
- method: "post",
- path: "/oko_admin/v1/user/login",
- tags: ["Admin"],
- summary: "Admin login",
- description: "Authenticates an admin user",
- security: [],
- request: {
- body: {
- required: true,
- content: {
- "application/json": {
- schema: LoginRequestSchema,
- },
- },
- },
- },
- responses: {
- 200: {
- description: "Successfully logged in",
- content: {
- "application/json": {
- schema: AdminLoginSuccessResponseSchema,
- },
- },
- },
- 400: {
- description: "Invalid request",
- content: {
- "application/json": {
- schema: ErrorResponseSchema,
- },
- },
- },
- 401: {
- description: "Invalid credentials",
- content: {
- "application/json": {
- schema: ErrorResponseSchema,
- },
- },
- },
- 500: {
- description: "Server error",
- content: {
- "application/json": {
- schema: ErrorResponseSchema,
- },
- },
- },
- },
- });
- router.post(
- "/user/login",
- async (
- req: Request,
- res: Response>,
- ) => {
- const state = req.app.locals;
-
- const result = await login(state.db, req.body, {
- secret: state.jwt_secret,
- expires_in: state.jwt_expires_in,
- });
- if (result.success === false) {
- res.status(ErrorCodeMap[result.code] ?? 500).json(result);
- return;
- }
-
- res.status(200).json(result);
- },
- );
-
- registry.registerPath({
- method: "post",
- path: "/oko_admin/v1/user/logout",
- tags: ["Admin"],
- summary: "Admin logout",
- description: "Logs out an admin user",
- security: [{ adminAuth: [] }],
- request: {
- headers: AdminAuthHeaderSchema,
- },
- responses: {
- 200: {
- description: "Successfully logged out",
- content: {
- "application/json": {
- schema: AdminLogoutSuccessResponseSchema,
- },
- },
- },
- 401: {
- description: "Unauthorized",
- content: {
- "application/json": {
- schema: ErrorResponseSchema,
- },
- },
- },
- 500: {
- description: "Server error",
- content: {
- "application/json": {
- schema: ErrorResponseSchema,
- },
- },
- },
- },
- });
- router.post(
- "/user/logout",
- adminAuthMiddleware,
- async (
- req: Request,
- res: Response>,
- ) => {
- const state = req.app.locals;
-
- const authHeader = req.headers.authorization;
- const token =
- authHeader && authHeader.startsWith("Bearer ")
- ? authHeader.substring(7)
- : undefined;
-
- const result = await logout(state.db, token);
- if (result.success === false) {
- res.status(ErrorCodeMap[result.code] ?? 500).json(result);
- return;
- }
-
- res.status(200).json(result);
- },
- );
-}
diff --git a/backend/admin_api/src/routes/user_login.ts b/backend/admin_api/src/routes/user_login.ts
new file mode 100644
index 000000000..996a7e762
--- /dev/null
+++ b/backend/admin_api/src/routes/user_login.ts
@@ -0,0 +1,42 @@
+import type { Response, Request } from "express";
+import { v4 as uuidv4 } from "uuid";
+import type {
+ AdminLoginRequest,
+ AdminLoginResponse,
+} from "@oko-wallet/oko-types/admin";
+import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
+import { ErrorCodeMap } from "@oko-wallet/oko-api-error-codes";
+
+import { type AuditContext } from "@oko-wallet-admin-api/utils/audit";
+import { login } from "@oko-wallet-admin-api/api/user";
+
+export async function user_login(
+ req: Request,
+ res: Response>,
+) {
+ const state = req.app.locals;
+
+ // Create audit context for login route
+ const auditContext: AuditContext = {
+ db: state.db,
+ adminUserId: undefined, // Not authenticated yet
+ request: req,
+ requestId: uuidv4(),
+ };
+
+ const result = await login(
+ state.db,
+ req.body,
+ {
+ secret: state.jwt_secret,
+ expires_in: state.jwt_expires_in,
+ },
+ auditContext,
+ );
+ if (result.success === false) {
+ res.status(ErrorCodeMap[result.code] ?? 500).json(result);
+ return;
+ }
+
+ res.status(200).json(result);
+}
diff --git a/backend/admin_api/src/routes/user_logout.ts b/backend/admin_api/src/routes/user_logout.ts
new file mode 100644
index 000000000..971618951
--- /dev/null
+++ b/backend/admin_api/src/routes/user_logout.ts
@@ -0,0 +1,28 @@
+import type { Response } from "express";
+import type { AdminLogoutResponse } from "@oko-wallet/oko-types/admin";
+import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
+import { ErrorCodeMap } from "@oko-wallet/oko-api-error-codes";
+
+import { type AuthenticatedAdminRequest } from "@oko-wallet-admin-api/middleware";
+import { logout } from "@oko-wallet-admin-api/api/user";
+
+export async function user_logout(
+ req: AuthenticatedAdminRequest,
+ res: Response>,
+) {
+ const state = req.app.locals;
+
+ const authHeader = req.headers.authorization;
+ const token =
+ authHeader && authHeader.startsWith("Bearer ")
+ ? authHeader.substring(7)
+ : undefined;
+
+ const result = await logout(state.db, token, req.auditContext);
+ if (result.success === false) {
+ res.status(ErrorCodeMap[result.code] ?? 500).json(result);
+ return;
+ }
+
+ res.status(200).json(result);
+}
diff --git a/backend/admin_api/src/utils/audit.ts b/backend/admin_api/src/utils/audit.ts
new file mode 100644
index 000000000..cc5a5a3c6
--- /dev/null
+++ b/backend/admin_api/src/utils/audit.ts
@@ -0,0 +1,46 @@
+import { v4 as uuidv4 } from "uuid";
+import type { CreateAuditEventRequest } from "@oko-wallet/oko-types/admin";
+import { createAuditEvent } from "@oko-wallet/oko-pg-interface/audit_events";
+
+export interface AuditContext {
+ db: any;
+ adminUserId?: string;
+ request?: any;
+ requestId?: string;
+}
+
+export function resolveAuditActorId(context: AuditContext): string {
+ if (context.adminUserId) {
+ return `admin:${context.adminUserId}`;
+ } else {
+ return "system";
+ }
+}
+
+export async function createAuditLog(
+ context: AuditContext,
+ action: string,
+ targetType: string,
+ targetId?: string,
+ changes?: { field: string; from?: any; to?: any }[],
+ params?: Record,
+ outcome: "success" | "failure" | "denied" = "success",
+ error?: string,
+) {
+ const auditData: CreateAuditEventRequest = {
+ request_id: context.requestId || uuidv4(),
+ actor: resolveAuditActorId(context),
+ actor_ip: context.request?.ip || undefined,
+ user_agent: context.request?.get?.("User-Agent") || undefined,
+ source: context.request ? "admin_ui" : "job",
+ action,
+ target_type: targetType,
+ target_id: targetId,
+ changes,
+ params: params,
+ outcome,
+ error,
+ };
+
+ return await createAuditEvent(context.db, auditData);
+}
diff --git a/backend/ct_dashboard_api/src/middleware/index.ts b/backend/ct_dashboard_api/src/middleware/index.ts
index 74d71d9db..bac4263c1 100644
--- a/backend/ct_dashboard_api/src/middleware/index.ts
+++ b/backend/ct_dashboard_api/src/middleware/index.ts
@@ -59,11 +59,9 @@ export async function customerJwtMiddleware(
next();
return;
} catch (error) {
- res
- .status(500)
- .json({
- error: `Token validation failed: ${error instanceof Error ? error.message : String(error)}`,
- });
+ res.status(500).json({
+ error: `Token validation failed: ${error instanceof Error ? error.message : String(error)}`,
+ });
return;
}
}
diff --git a/backend/ct_dashboard_api/src/routes/customer_auth.ts b/backend/ct_dashboard_api/src/routes/customer_auth.ts
index fc8ffbf87..ff883196d 100644
--- a/backend/ct_dashboard_api/src/routes/customer_auth.ts
+++ b/backend/ct_dashboard_api/src/routes/customer_auth.ts
@@ -98,10 +98,7 @@ export function setCustomerAuthRoutes(router: Router) {
});
router.post(
"/customer/auth/send-code",
- async (
- req,
- res: Response>,
- ) => {
+ async (req, res: Response>) => {
try {
const state = req.app.locals;
const request: SendVerificationRequest = {
diff --git a/backend/ks_node_health/src/index.ts b/backend/ks_node_health/src/index.ts
index 70acfbc25..4cfabf463 100644
--- a/backend/ks_node_health/src/index.ts
+++ b/backend/ks_node_health/src/index.ts
@@ -48,9 +48,7 @@ export async function processKSNodeHealthChecks(
};
}
-export async function requestKSNodeHealthCheck(
- cvEndpoint: string,
-): Promise {
+async function requestKSNodeHealthCheck(cvEndpoint: string): Promise {
try {
const response = await fetch(`${cvEndpoint}/`);
if (response.status !== 200) {
diff --git a/backend/log_api/src/api/log/index.ts b/backend/log_api/src/api/log/index.ts
index 16ded4ee3..0057dbcf7 100644
--- a/backend/log_api/src/api/log/index.ts
+++ b/backend/log_api/src/api/log/index.ts
@@ -1,9 +1,6 @@
import type { Logger } from "winston";
import { z } from "zod";
-import type {
- PostLogBody,
- PostLogResponse,
-} from "@oko-wallet/oko-types/log";
+import type { PostLogBody, PostLogResponse } from "@oko-wallet/oko-types/log";
import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
export function ingestLog(
diff --git a/backend/log_api/src/routes/log.ts b/backend/log_api/src/routes/log.ts
index 609ee1e97..6558c9ec2 100644
--- a/backend/log_api/src/routes/log.ts
+++ b/backend/log_api/src/routes/log.ts
@@ -1,10 +1,7 @@
import type { Response, Router, Request } from "express";
import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
import type { Logger } from "winston";
-import type {
- PostLogBody,
- PostLogResponse,
-} from "@oko-wallet/oko-types/log";
+import type { PostLogBody, PostLogResponse } from "@oko-wallet/oko-types/log";
import { ErrorCodeMap } from "@oko-wallet/oko-api-error-codes";
import { ingestLog } from "@oko-wallet-log-api/api/log";
diff --git a/backend/oko_pg_interface/package.json b/backend/oko_pg_interface/package.json
index c49866275..8979dca9f 100644
--- a/backend/oko_pg_interface/package.json
+++ b/backend/oko_pg_interface/package.json
@@ -8,6 +8,7 @@
"exports": {
"./admin_users": "./src/admin_users/index.ts",
"./api_keys": "./src/api_keys/index.ts",
+ "./audit_events": "./src/audit_events/index.ts",
"./ks_nodes": "./src/ks_nodes/index.ts",
"./customer_dashboard_users": "./src/customer_dashboard_users/index.ts",
"./customers": "./src/customers/index.ts",
diff --git a/backend/oko_pg_interface/scripts/migrate/migrate.sql b/backend/oko_pg_interface/scripts/migrate/migrate.sql
index f525533fe..cb630e076 100644
--- a/backend/oko_pg_interface/scripts/migrate/migrate.sql
+++ b/backend/oko_pg_interface/scripts/migrate/migrate.sql
@@ -264,3 +264,32 @@ CREATE TABLE public.tss_activation_settings (
updated_at timestamptz DEFAULT now() NOT NULL,
CONSTRAINT tss_activation_settings_pkey PRIMARY KEY (activation_key)
);
+
+-- public.audit_event definition
+
+-- Drop table
+
+-- DROP TABLE public.audit_event;
+
+CREATE TABLE audit_event (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ occurred_at TIMESTAMPTZ NOT NULL DEFAULT now(),
+ request_id TEXT NOT NULL,
+ actor TEXT NOT NULL,
+ actor_ip INET,
+ user_agent TEXT,
+ source TEXT NOT NULL,
+ action TEXT NOT NULL,
+ target_type TEXT NOT NULL,
+ target_id TEXT,
+ changes JSONB,
+ params JSONB,
+ outcome TEXT NOT NULL,
+ error TEXT
+);
+
+-- Indexes for audit_event
+CREATE INDEX ON audit_event (occurred_at DESC);
+CREATE INDEX ON audit_event (target_type, target_id, occurred_at DESC);
+CREATE INDEX ON audit_event (action, occurred_at DESC);
+CREATE UNIQUE INDEX audit_event_unique_req ON audit_event (request_id, action, target_type, target_id);
diff --git a/backend/oko_pg_interface/src/audit_events/index.ts b/backend/oko_pg_interface/src/audit_events/index.ts
new file mode 100644
index 000000000..1b71794de
--- /dev/null
+++ b/backend/oko_pg_interface/src/audit_events/index.ts
@@ -0,0 +1,222 @@
+import { Pool, type PoolClient } from "pg";
+import { v4 as uuidv4 } from "uuid";
+import type { Result } from "@oko-wallet/stdlib-js";
+import type {
+ AuditEvent,
+ CreateAuditEventRequest,
+ AuditEventFilter,
+} from "@oko-wallet/oko-types/admin";
+
+export async function createAuditEvent(
+ db: Pool | PoolClient,
+ request: CreateAuditEventRequest,
+): Promise> {
+ const query = `
+INSERT INTO audit_event (
+ id, occurred_at, request_id, actor,
+ actor_ip, user_agent, source, action,
+ target_type, target_id, changes, params,
+ outcome, error
+)
+VALUES (
+ $1, $2, $3, $4,
+ $5, $6, $7, $8,
+ $9, $10, $11, $12,
+ $13, $14
+)
+RETURNING *
+`;
+
+ const values = [
+ uuidv4(),
+ new Date(),
+ request.request_id,
+ request.actor,
+ request.actor_ip || null,
+ request.user_agent || null,
+ request.source,
+ request.action,
+ request.target_type,
+ request.target_id || null,
+ request.changes ? JSON.stringify(request.changes) : null,
+ request.params ? JSON.stringify(request.params) : null,
+ request.outcome,
+ request.error || null,
+ ];
+
+ try {
+ const result = await db.query(query, values);
+
+ const row = result.rows[0];
+ if (!row) {
+ return {
+ success: false,
+ err: "Failed to create audit event",
+ };
+ }
+
+ return {
+ success: true,
+ data: {
+ ...row,
+ changes: row.changes || undefined,
+ params: row.params || undefined,
+ },
+ };
+ } catch (error) {
+ return {
+ success: false,
+ err: String(error),
+ };
+ }
+}
+
+export async function getAuditEvents(
+ db: Pool,
+ filter: AuditEventFilter,
+): Promise> {
+ let query = `
+SELECT *
+FROM audit_event
+WHERE 1=1
+`;
+
+ const values: any[] = [];
+ let paramIndex = 1;
+
+ if (filter.target_type) {
+ query += ` AND target_type = $${paramIndex++}`;
+ values.push(filter.target_type);
+ }
+
+ if (filter.target_id) {
+ query += ` AND target_id = $${paramIndex++}`;
+ values.push(filter.target_id);
+ }
+
+ if (filter.action) {
+ query += ` AND action = $${paramIndex++}`;
+ values.push(filter.action);
+ }
+
+ if (filter.actor) {
+ query += ` AND actor = $${paramIndex++}`;
+ values.push(filter.actor);
+ }
+
+ if (filter.source) {
+ query += ` AND source = $${paramIndex++}`;
+ values.push(filter.source);
+ }
+
+ if (filter.outcome) {
+ query += ` AND outcome = $${paramIndex++}`;
+ values.push(filter.outcome);
+ }
+
+ if (filter.occurred_after) {
+ query += ` AND occurred_at >= $${paramIndex++}`;
+ values.push(filter.occurred_after);
+ }
+
+ if (filter.occurred_before) {
+ query += ` AND occurred_at <= $${paramIndex++}`;
+ values.push(filter.occurred_before);
+ }
+
+ query += ` ORDER BY occurred_at DESC`;
+
+ if (filter.limit) {
+ query += ` LIMIT $${paramIndex++}`;
+ values.push(filter.limit);
+ }
+
+ if (filter.offset) {
+ query += ` OFFSET $${paramIndex++}`;
+ values.push(filter.offset);
+ }
+
+ try {
+ const result = await db.query(query, values);
+
+ const events = result.rows.map((row) => ({
+ ...row,
+ changes: (row.changes as AuditEvent["changes"]) || undefined,
+ params: (row.params as AuditEvent["params"]) || undefined,
+ }));
+
+ return {
+ success: true,
+ data: events,
+ };
+ } catch (error) {
+ return {
+ success: false,
+ err: String(error),
+ };
+ }
+}
+
+export async function getAuditEventsCount(
+ db: Pool,
+ filter: AuditEventFilter,
+): Promise> {
+ let query = `
+SELECT COUNT(*)
+FROM audit_event
+WHERE 1=1
+`;
+
+ const values: any[] = [];
+ let paramIndex = 1;
+
+ if (filter.target_type) {
+ query += ` AND target_type = $${paramIndex++}`;
+ values.push(filter.target_type);
+ }
+
+ if (filter.target_id) {
+ query += ` AND target_id = $${paramIndex++}`;
+ values.push(filter.target_id);
+ }
+
+ if (filter.action) {
+ query += ` AND action = $${paramIndex++}`;
+ values.push(filter.action);
+ }
+
+ if (filter.actor) {
+ query += ` AND actor = $${paramIndex++}`;
+ values.push(filter.actor);
+ }
+
+ if (filter.source) {
+ query += ` AND source = $${paramIndex++}`;
+ values.push(filter.source);
+ }
+
+ if (filter.outcome) {
+ query += ` AND outcome = $${paramIndex++}`;
+ values.push(filter.outcome);
+ }
+
+ if (filter.occurred_after) {
+ query += ` AND occurred_at >= $${paramIndex++}`;
+ values.push(filter.occurred_after);
+ }
+
+ if (filter.occurred_before) {
+ query += ` AND occurred_at <= $${paramIndex++}`;
+ values.push(filter.occurred_before);
+ }
+
+ try {
+ const result = await db.query(query, values);
+ return { success: true, data: parseInt(result.rows[0].count) };
+ } catch (error) {
+ return {
+ success: false,
+ err: String(error),
+ };
+ }
+}
diff --git a/backend/openapi/src/oko_admin/audit.ts b/backend/openapi/src/oko_admin/audit.ts
new file mode 100644
index 000000000..27b514322
--- /dev/null
+++ b/backend/openapi/src/oko_admin/audit.ts
@@ -0,0 +1,226 @@
+import { z } from "zod";
+
+export const GetAuditLogsQuerySchema = z.object({
+ target_type: z
+ .string()
+ .optional()
+ .openapi({
+ description: "Filter by target type",
+ example: "customer",
+ param: {
+ name: "target_type",
+ in: "query",
+ required: false,
+ },
+ }),
+ target_id: z
+ .string()
+ .optional()
+ .openapi({
+ description: "Filter by target ID",
+ example: "123e4567-e89b-12d3-a456-426614174000",
+ param: {
+ name: "target_id",
+ in: "query",
+ required: false,
+ },
+ }),
+ action: z
+ .string()
+ .optional()
+ .openapi({
+ description: "Filter by action type",
+ example: "create",
+ param: {
+ name: "action",
+ in: "query",
+ required: false,
+ },
+ }),
+ actor: z
+ .string()
+ .optional()
+ .openapi({
+ description: "Filter by actor",
+ example: "admin:123e4567-e89b-12d3-a456-426614174000",
+ param: {
+ name: "actor",
+ in: "query",
+ required: false,
+ },
+ }),
+ source: z
+ .string()
+ .optional()
+ .openapi({
+ description: "Filter by source",
+ example: "admin_ui",
+ param: {
+ name: "source",
+ in: "query",
+ required: false,
+ },
+ }),
+ outcome: z
+ .enum(["success", "failure", "denied"])
+ .optional()
+ .openapi({
+ description: "Filter by outcome",
+ example: "success",
+ param: {
+ name: "outcome",
+ in: "query",
+ required: false,
+ },
+ }),
+ occurred_after: z.iso
+ .datetime()
+ .optional()
+ .openapi({
+ description: "Filter for events after this timestamp",
+ example: "2023-01-01T00:00:00Z",
+ param: {
+ name: "occurred_after",
+ in: "query",
+ required: false,
+ },
+ }),
+ occurred_before: z.iso
+ .datetime()
+ .optional()
+ .openapi({
+ description: "Filter for events before this timestamp",
+ example: "2023-12-31T23:59:59Z",
+ param: {
+ name: "occurred_before",
+ in: "query",
+ required: false,
+ },
+ }),
+ limit: z.coerce
+ .number()
+ .int()
+ .optional()
+ .openapi({
+ description: "Number of results to return (default: 20)",
+ example: 20,
+ param: {
+ name: "limit",
+ in: "query",
+ required: false,
+ },
+ }),
+ offset: z.coerce
+ .number()
+ .int()
+ .optional()
+ .openapi({
+ description: "Number of results to skip (for pagination)",
+ example: 0,
+ param: {
+ name: "offset",
+ in: "query",
+ required: false,
+ },
+ }),
+});
+
+export const GetAuditLogsCountQuerySchema = z.object({
+ target_type: z
+ .string()
+ .optional()
+ .openapi({
+ description: "Filter by target type",
+ example: "customer",
+ param: {
+ name: "target_type",
+ in: "query",
+ required: false,
+ },
+ }),
+ target_id: z
+ .string()
+ .optional()
+ .openapi({
+ description: "Filter by target ID",
+ example: "123e4567-e89b-12d3-a456-426614174000",
+ param: {
+ name: "target_id",
+ in: "query",
+ required: false,
+ },
+ }),
+ action: z
+ .string()
+ .optional()
+ .openapi({
+ description: "Filter by action type",
+ example: "create",
+ param: {
+ name: "action",
+ in: "query",
+ required: false,
+ },
+ }),
+ actor: z
+ .string()
+ .optional()
+ .openapi({
+ description: "Filter by actor",
+ example: "admin:123e4567-e89b-12d3-a456-426614174000",
+ param: {
+ name: "actor",
+ in: "query",
+ required: false,
+ },
+ }),
+ source: z
+ .string()
+ .optional()
+ .openapi({
+ description: "Filter by source",
+ example: "admin_ui",
+ param: {
+ name: "source",
+ in: "query",
+ required: false,
+ },
+ }),
+ outcome: z
+ .enum(["success", "failure", "denied"])
+ .optional()
+ .openapi({
+ description: "Filter by outcome",
+ example: "success",
+ param: {
+ name: "outcome",
+ in: "query",
+ required: false,
+ },
+ }),
+ occurred_after: z
+ .string()
+ .datetime()
+ .optional()
+ .openapi({
+ description: "Filter for events after this timestamp",
+ example: "2023-01-01T00:00:00Z",
+ param: {
+ name: "occurred_after",
+ in: "query",
+ required: false,
+ },
+ }),
+ occurred_before: z.iso
+ .datetime()
+ .optional()
+ .openapi({
+ description: "Filter for events before this timestamp",
+ example: "2023-12-31T23:59:59Z",
+ param: {
+ name: "occurred_before",
+ in: "query",
+ required: false,
+ },
+ }),
+});
diff --git a/backend/openapi/src/oko_admin/index.ts b/backend/openapi/src/oko_admin/index.ts
index fcfe49936..4703bc754 100644
--- a/backend/openapi/src/oko_admin/index.ts
+++ b/backend/openapi/src/oko_admin/index.ts
@@ -1,3 +1,4 @@
+export * from "./audit";
export * from "./customer";
export * from "./tss";
export * from "./user";
diff --git a/backend/tss_api/src/api/keygen/index.ts b/backend/tss_api/src/api/keygen/index.ts
index a0a142e45..65ce07d0d 100644
--- a/backend/tss_api/src/api/keygen/index.ts
+++ b/backend/tss_api/src/api/keygen/index.ts
@@ -6,10 +6,7 @@ import {
import type { Result } from "@oko-wallet/stdlib-js";
import { encryptData } from "@oko-wallet/crypto-js";
import { Bytes, type Bytes33 } from "@oko-wallet/bytes";
-import {
- type WalletStatus,
- type Wallet,
-} from "@oko-wallet/oko-types/wallets";
+import { type WalletStatus, type Wallet } from "@oko-wallet/oko-types/wallets";
import type { KeygenRequest } from "@oko-wallet/oko-types/tss";
import type { SignInResponse, User } from "@oko-wallet/oko-types/user";
import type { OkoApiResponse } from "@oko-wallet/oko-types/api_response";
diff --git a/backend/tss_api/src/api/triples/index.ts b/backend/tss_api/src/api/triples/index.ts
index 251993d64..48ecf88ec 100644
--- a/backend/tss_api/src/api/triples/index.ts
+++ b/backend/tss_api/src/api/triples/index.ts
@@ -24,10 +24,7 @@ import type {
TriplesStep9Response,
TriplesStageData,
} from "@oko-wallet/oko-types/tss";
-import {
- TriplesStageStatus,
- TssStageType,
-} from "@oko-wallet/oko-types/tss";
+import { TriplesStageStatus, TssStageType } from "@oko-wallet/oko-types/tss";
import {
createTssSession,
createTssStage,
diff --git a/backend/tss_api/src/api/tss_session/index.test.ts b/backend/tss_api/src/api/tss_session/index.test.ts
index 06d2db8be..1514c8c68 100644
--- a/backend/tss_api/src/api/tss_session/index.test.ts
+++ b/backend/tss_api/src/api/tss_session/index.test.ts
@@ -7,10 +7,7 @@ import { createTssSession } from "@oko-wallet/oko-pg-interface/tss";
import { insertCustomer } from "@oko-wallet/oko-pg-interface/customers";
import { createWallet } from "@oko-wallet/oko-pg-interface/ewallet_wallets";
import { createUser } from "@oko-wallet/oko-pg-interface/ewallet_users";
-import {
- TssStageType,
- TriplesStageStatus,
-} from "@oko-wallet/oko-types/tss";
+import { TssStageType, TriplesStageStatus } from "@oko-wallet/oko-types/tss";
import {
napiRunTriples2ClientStep1,
napiRunTriples2ClientStep2,
diff --git a/biome.json b/biome.json
index d01f5d69f..c3c319d94 100644
--- a/biome.json
+++ b/biome.json
@@ -1,5 +1,5 @@
{
- "$schema": "https://biomejs.dev/schemas/2.2.6/schema.json",
+ "$schema": "https://biomejs.dev/schemas/2.3.4/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
@@ -28,7 +28,14 @@
"linter": {
"enabled": false,
"rules": {
- "recommended": true
+ "recommended": true,
+ "style": {
+ "noNonNullAssertion": "off",
+ "useTemplate": "off"
+ },
+ "suspicious": {
+ "noExplicitAny": "off"
+ }
}
},
"javascript": {
@@ -49,6 +56,11 @@
"trailingCommas": "none"
}
},
+ "css": {
+ "parser": {
+ "tailwindDirectives": true
+ }
+ },
"assist": {
"enabled": true,
"actions": {
@@ -79,10 +91,11 @@
"recommended": true,
"style": {
"useConst": "error",
- "useImportType": "off"
+ "useImportType": "off",
+ "noNonNullAssertion": "off"
},
"suspicious": {
- "noExplicitAny": "warn"
+ "noExplicitAny": "off"
},
"correctness": {
"noUnusedVariables": "error"
diff --git a/common/oko_types/package.json b/common/oko_types/package.json
index dab25163d..5eca76a43 100644
--- a/common/oko_types/package.json
+++ b/common/oko_types/package.json
@@ -5,6 +5,7 @@
"./admin": "./src/admin/index.ts",
"./api_response": "./src/api_response/index.ts",
"./auth": "./src/auth/index.ts",
+ "./audit": "./src/audit/index.ts",
"./crypto": "./src/crypto/index.ts",
"./ct_dashboard": "./src/ct_dashboard/index.ts",
"./customers": "./src/customers/index.ts",
diff --git a/common/oko_types/src/admin/audit.ts b/common/oko_types/src/admin/audit.ts
new file mode 100644
index 000000000..3dc350e54
--- /dev/null
+++ b/common/oko_types/src/admin/audit.ts
@@ -0,0 +1,58 @@
+export interface AuditEvent {
+ id: string;
+ occurred_at: Date;
+ request_id: string;
+ actor: string;
+ actor_ip?: string;
+ user_agent?: string;
+ source: string;
+ action: string;
+ target_type: string;
+ target_id?: string;
+ changes?: AuditChange[];
+ params?: Record;
+ outcome: "success" | "failure" | "denied";
+ error?: string;
+}
+
+export interface AuditChange {
+ field: string;
+ from?: any;
+ to?: any;
+}
+
+export interface CreateAuditEventRequest {
+ request_id: string;
+ actor: string;
+ actor_ip?: string;
+ user_agent?: string;
+ source: string;
+ action: string;
+ target_type: string;
+ target_id?: string;
+ changes?: AuditChange[];
+ params?: Record;
+ outcome: "success" | "failure" | "denied";
+ error?: string;
+}
+
+export interface AuditEventFilter {
+ target_type?: string;
+ target_id?: string;
+ action?: string;
+ actor?: string;
+ source?: string;
+ outcome?: "success" | "failure" | "denied";
+ occurred_after?: Date;
+ occurred_before?: Date;
+ limit?: number;
+ offset?: number;
+}
+
+export interface GetAuditLogsResponse {
+ audit_logs: AuditEvent[];
+}
+
+export interface GetAuditLogsCountResponse {
+ count: number;
+}
diff --git a/common/oko_types/src/admin/index.ts b/common/oko_types/src/admin/index.ts
index b033ae48a..adb7426f9 100644
--- a/common/oko_types/src/admin/index.ts
+++ b/common/oko_types/src/admin/index.ts
@@ -1,4 +1,5 @@
export * from "./auth";
+export * from "./audit";
export * from "./customer";
export * from "./user";
export * from "./ks_node";
diff --git a/common/oko_types/src/api_response/index.ts b/common/oko_types/src/api_response/index.ts
index 32abfcbba..325b99cf1 100644
--- a/common/oko_types/src/api_response/index.ts
+++ b/common/oko_types/src/api_response/index.ts
@@ -1,6 +1,4 @@
-export type OkoApiResponse =
- | OkoApiSuccessResponse
- | OkoApiErrorResponse;
+export type OkoApiResponse = OkoApiSuccessResponse | OkoApiErrorResponse;
export interface OkoApiSuccessResponse {
success: true;
diff --git a/common/oko_types/src/ct_dashboard/customer_dashboard_user.ts b/common/oko_types/src/ct_dashboard/customer_dashboard_user.ts
index 6df367d20..6b896ddf1 100644
--- a/common/oko_types/src/ct_dashboard/customer_dashboard_user.ts
+++ b/common/oko_types/src/ct_dashboard/customer_dashboard_user.ts
@@ -1,7 +1,4 @@
-import type {
- Customer,
- CustomerStatus,
-} from "@oko-wallet/oko-types/customers";
+import type { Customer, CustomerStatus } from "@oko-wallet/oko-types/customers";
export type CustomerDashboardUserStatus = "ACTIVE" | "DELETED";
diff --git a/crypto/tecdsa/api_lib/src/index.ts b/crypto/tecdsa/api_lib/src/index.ts
index af04cefd6..30143b638 100644
--- a/crypto/tecdsa/api_lib/src/index.ts
+++ b/crypto/tecdsa/api_lib/src/index.ts
@@ -499,13 +499,12 @@ export async function reqAbortTssSession(
payload: AbortTssSessionBody,
authToken: string,
) {
- const resp: OkoApiResponse =
- await makePostRequest(
- endpoint,
- "session/abort",
- payload,
- undefined,
- authToken,
- );
+ const resp: OkoApiResponse = await makePostRequest(
+ endpoint,
+ "session/abort",
+ payload,
+ undefined,
+ authToken,
+ );
return resp;
}
diff --git a/crypto/tecdsa/cait_sith_keplr_addon/src/tests/sign_test.ts b/crypto/tecdsa/cait_sith_keplr_addon/src/tests/sign_test.ts
index 39b50542f..e9d3324fb 100644
--- a/crypto/tecdsa/cait_sith_keplr_addon/src/tests/sign_test.ts
+++ b/crypto/tecdsa/cait_sith_keplr_addon/src/tests/sign_test.ts
@@ -56,5 +56,6 @@ export function signTest(
throw new Error("sign failed. big_r or s do not match");
}
- runVerify(fullSignature0, clientState.keygenOutput?.public_key!, msg);
+ const output = clientState.keygenOutput!;
+ runVerify(fullSignature0, output.public_key, msg);
}
diff --git a/crypto/tecdsa/cait_sith_keplr_hooks/src/sign.ts b/crypto/tecdsa/cait_sith_keplr_hooks/src/sign.ts
index dbc0e8f22..69e75f64c 100644
--- a/crypto/tecdsa/cait_sith_keplr_hooks/src/sign.ts
+++ b/crypto/tecdsa/cait_sith_keplr_hooks/src/sign.ts
@@ -6,10 +6,7 @@ import type {
} from "@oko-wallet/tecdsa-interface";
import { reqSignStep1, reqSignStep2 } from "@oko-wallet/api-lib";
import { wasmModule } from "@oko-wallet/cait-sith-keplr-wasm";
-import type {
- SignStep1Body,
- SignStep2Body,
-} from "@oko-wallet/oko-types/tss";
+import type { SignStep1Body, SignStep2Body } from "@oko-wallet/oko-types/tss";
import type { Result } from "@oko-wallet/stdlib-js";
export type RunSignError = { type: "aborted" } | { type: "error"; msg: string };
diff --git a/embed/oko_attached/oko_attached.env.example b/embed/oko_attached/oko_attached.env.example
index 4586b3bc5..5ec94bbad 100644
--- a/embed/oko_attached/oko_attached.env.example
+++ b/embed/oko_attached/oko_attached.env.example
@@ -5,3 +5,4 @@ VITE_DEMO_WEB_ORIGIN=http://localhost:3200
VITE_KEPLR_API_ENDPOINT=https://keplr-api.keplr.app
VITE_TX_INTERPRETER_API_ENDPOINT=https://tx-interpreter.keplr.app
VITE_AMPLITUDE_API_KEY=
+VITE_IPFS_GATEWAY_URL=
\ No newline at end of file
diff --git a/embed/oko_attached/src/analytics/amplitude.ts b/embed/oko_attached/src/analytics/amplitude.ts
index e4bd03bd3..a18d3a765 100644
--- a/embed/oko_attached/src/analytics/amplitude.ts
+++ b/embed/oko_attached/src/analytics/amplitude.ts
@@ -5,7 +5,7 @@ let isInitialized = false;
export function initAmplitude(): void {
const apiKey = import.meta.env.VITE_AMPLITUDE_API_KEY;
- if (!apiKey) {
+ if (!apiKey || apiKey.trim() === "") {
console.warn("[attached] Amplitude. API key not found");
return;
diff --git a/embed/oko_attached/src/components/google_callback/google_callback.tsx b/embed/oko_attached/src/components/google_callback/google_callback.tsx
index 1108437c9..1cc0bb697 100644
--- a/embed/oko_attached/src/components/google_callback/google_callback.tsx
+++ b/embed/oko_attached/src/components/google_callback/google_callback.tsx
@@ -4,7 +4,7 @@ import React, { useEffect, useState } from "react";
import { LoadingIcon } from "@oko-wallet/oko-common-ui/icons/loading";
import { Spacing } from "@oko-wallet/oko-common-ui/spacing";
import { Typography } from "@oko-wallet/oko-common-ui/typography";
-import { OkoProductLogoIcon } from "@oko-wallet-common-ui/icons/oko_product_logo_icon";
+import { OkoLogoIcon } from "@oko-wallet-common-ui/icons/oko_logo_icon";
import { ErrorIcon } from "@oko-wallet/oko-common-ui/icons/error_icon";
import { ExternalLinkOutlinedIcon } from "@oko-wallet/oko-common-ui/icons/external_link_outlined";
import type { Theme } from "@oko-wallet/oko-common-ui/theme";
@@ -27,7 +27,7 @@ export const GoogleCallback: React.FC = () => {
return (
-
+
{error ? (
diff --git a/embed/oko_attached/src/components/modal/modal.tsx b/embed/oko_attached/src/components/modal/modal.tsx
index d73f6479a..e6d6f7ee6 100644
--- a/embed/oko_attached/src/components/modal/modal.tsx
+++ b/embed/oko_attached/src/components/modal/modal.tsx
@@ -26,6 +26,7 @@ export const Modal: FC = () => {
console.log("onOpenChange(): %s", open);
if (modalRequest) {
+ // TODO: @hyunjae
const { modal_type, modal_id } = modalRequest.msg.payload;
const { error } = useMemoryState.getState();
diff --git a/embed/oko_attached/src/components/modal_variants/cosmos/arbitrary_sig/make_arbitrary_sig_modal.tsx b/embed/oko_attached/src/components/modal_variants/cosmos/arbitrary_sig/make_arbitrary_sig_modal.tsx
index ba071552b..b900b5199 100644
--- a/embed/oko_attached/src/components/modal_variants/cosmos/arbitrary_sig/make_arbitrary_sig_modal.tsx
+++ b/embed/oko_attached/src/components/modal_variants/cosmos/arbitrary_sig/make_arbitrary_sig_modal.tsx
@@ -3,8 +3,6 @@ import type { CosmosArbitrarySigData } from "@oko-wallet/oko-sdk-core";
import { XCloseIcon } from "@oko-wallet/oko-common-ui/icons/x_close";
import { Spacing } from "@oko-wallet/oko-common-ui/spacing";
import { Button } from "@oko-wallet/oko-common-ui/button";
-import { Typography } from "@oko-wallet/oko-common-ui/typography";
-import { OkoProductLogoIcon } from "@oko-wallet/oko-common-ui/icons/oko_product_logo_icon";
import styles from "@oko-wallet-attached/components/modal_variants/common/make_signature/make_signature_modal.module.scss";
import { CommonModal } from "@oko-wallet-attached/components/modal_variants/common/common_modal";
@@ -12,6 +10,7 @@ import { DemoView } from "@oko-wallet-attached/components/modal_variants/common/
import { useArbitrarySigModal } from "./use_arbitrary_sig_modal";
import { ArbitrarySignatureDesc } from "@oko-wallet-attached/components/modal_variants/common/arbitrary_sig_desc/arbitrary_signature_desc";
import { CosmosArbitrarySignatureContent } from "./cosmos_arbitrary_signature_content";
+import { SignWithOkoBox } from "@oko-wallet-attached/components/sign_with_oko_box/sign_with_oko_box";
export const MakeArbitrarySigModal: React.FC = ({
getIsAborted,
@@ -63,12 +62,7 @@ export const MakeArbitrarySigModal: React.FC = ({
-
-
- Sign with
-
-
-
+
{isDemo && }
diff --git a/embed/oko_attached/src/components/modal_variants/cosmos/tx_sig/make_tx_sig_modal.tsx b/embed/oko_attached/src/components/modal_variants/cosmos/tx_sig/make_tx_sig_modal.tsx
index a2f7aff53..b3ec74cb6 100644
--- a/embed/oko_attached/src/components/modal_variants/cosmos/tx_sig/make_tx_sig_modal.tsx
+++ b/embed/oko_attached/src/components/modal_variants/cosmos/tx_sig/make_tx_sig_modal.tsx
@@ -3,8 +3,6 @@ import type { CosmosTxSigData } from "@oko-wallet/oko-sdk-core";
import { XCloseIcon } from "@oko-wallet/oko-common-ui/icons/x_close";
import { Spacing } from "@oko-wallet/oko-common-ui/spacing";
import { Button } from "@oko-wallet/oko-common-ui/button";
-import { Typography } from "@oko-wallet/oko-common-ui/typography";
-import { OkoProductLogoIcon } from "@oko-wallet/oko-common-ui/icons/oko_product_logo_icon";
import styles from "@oko-wallet-attached/components/modal_variants/common/make_signature/make_signature_modal.module.scss";
import { CommonModal } from "@oko-wallet-attached/components/modal_variants/common/common_modal";
@@ -14,6 +12,7 @@ import { CosmosTxSignatureContent } from "./cosmos_tx_signature_content";
import { CosmosTxFee } from "./cosmos_tx_fee";
import { extractMsgsFromSignDoc } from "@oko-wallet-attached/web3/cosmos/sign_doc";
import { trackTxButtonEvent } from "@oko-wallet-attached/analytics/events";
+import { SignWithOkoBox } from "@oko-wallet-attached/components/sign_with_oko_box/sign_with_oko_box";
export const MakeTxSigModal: FC = ({
getIsAborted,
@@ -125,12 +124,7 @@ export const MakeTxSigModal: FC = ({
-
-
- Sign with
-
-
-
+
>
);
} else {
diff --git a/embed/oko_attached/src/components/modal_variants/error/error_modal.module.scss b/embed/oko_attached/src/components/modal_variants/error/error_modal.module.scss
index 4d04d3948..7e98e162b 100644
--- a/embed/oko_attached/src/components/modal_variants/error/error_modal.module.scss
+++ b/embed/oko_attached/src/components/modal_variants/error/error_modal.module.scss
@@ -69,7 +69,7 @@
border: 1px solid var(--border-primary) !important;
}
-.productLogoIconContainer {
+.okoLogoIconContainer {
display: flex;
justify-content: center;
align-items: center;
diff --git a/embed/oko_attached/src/components/modal_variants/error/error_modal.tsx b/embed/oko_attached/src/components/modal_variants/error/error_modal.tsx
index 6c7d3dae6..54eea1b3a 100644
--- a/embed/oko_attached/src/components/modal_variants/error/error_modal.tsx
+++ b/embed/oko_attached/src/components/modal_variants/error/error_modal.tsx
@@ -4,13 +4,13 @@ import { Spacing } from "@oko-wallet/oko-common-ui/spacing";
import { Typography } from "@oko-wallet/oko-common-ui/typography";
import { XCloseIcon } from "@oko-wallet/oko-common-ui/icons/x_close";
import { Button } from "@oko-wallet/oko-common-ui/button";
-import { OkoProductLogoIcon } from "@oko-wallet/oko-common-ui/icons/oko_product_logo_icon";
import styles from "./error_modal.module.scss";
import { CommonModal } from "@oko-wallet-attached/components/modal_variants/common/common_modal";
import { useAppState } from "@oko-wallet-attached/store/app";
import { useMemoryState } from "@oko-wallet-attached/store/memory";
import type { AppError } from "@oko-wallet-attached/errors";
+import { SignWithOkoBox } from "@oko-wallet-attached/components/sign_with_oko_box/sign_with_oko_box";
function isAppErrorFn(error: unknown): error is AppError {
return typeof error === "object" && error !== null && "type" in error;
@@ -118,9 +118,7 @@ export const ErrorModal: FC = ({ error }) => {