1
0
forked from baron/baron-sso
Files
baron-sso/adminfront/src/lib/adminApi.ts
2026-06-15 20:05:47 +09:00

1406 lines
33 KiB
TypeScript

import { fetchAllCursorPages } from "../../../common/core/pagination";
import apiClient from "./apiClient";
import { userManager } from "./auth";
export type AuditLog = {
event_id: string;
timestamp: string;
user_id: string;
event_type: string;
status: string;
ip_address: string;
user_agent: string;
device_id?: string;
details?: string;
};
export type AuditLogListResponse = {
items: AuditLog[];
limit: number;
cursor?: string;
next_cursor?: string;
};
export type TenantSummary = {
id: string;
type: string; // 허용 타입: PERSONAL, COMPANY, COMPANY_GROUP, ORGANIZATION, USER_GROUP
name: string;
slug: string;
description: string;
status: string;
domains?: string[];
parentId?: string;
config?: Record<string, unknown>;
memberCount: number; // 해당 테넌트 직접 소속 인원
totalMemberCount?: number; // 하위 테넌트 포함 전체 인원
userPermissions?: {
view: boolean;
manage: boolean;
manage_admins: boolean;
view_profile?: boolean;
manage_profile?: boolean;
view_permissions?: boolean;
manage_permissions?: boolean;
view_organization?: boolean;
manage_organization?: boolean;
view_schema?: boolean;
manage_schema?: boolean;
};
createdAt: string;
updatedAt: string;
};
export type TenantCreateRequest = {
name: string;
type?: string;
slug?: string;
parentId?: string;
description?: string;
status?: string;
domains?: string[];
forceDomainConflicts?: string[];
config?: Record<string, unknown>;
};
export type TenantListResponse = {
items: TenantSummary[];
limit: number;
offset: number;
total: number;
cursor?: string;
nextCursor?: string;
next_cursor?: string;
};
export type TenantUpdateRequest = {
name?: string;
type?: string;
slug?: string;
parentId?: string;
description?: string;
status?: string;
domains?: string[];
forceDomainConflicts?: string[];
config?: Record<string, unknown>;
};
export type TenantImportDetail = {
row: number;
slug: string;
name: string;
success: boolean;
action: "created" | "updated" | "failed" | "skipped";
message: string;
modifiedFields?: string[];
};
export type TenantImportResult = {
created: number;
updated: number;
failed: number;
errors: string[];
details: TenantImportDetail[];
};
export type ApiKeySummary = {
id: string;
name: string;
client_id: string;
scopes: string[];
status: string;
lastUsedAt?: string;
createdAt: string;
};
export type ApiKeyListResponse = {
items: ApiKeySummary[];
total: number;
};
export type RoleSummary = {
id: string;
name: string;
description: string;
permissions: string[];
createdAt: string;
updatedAt: string;
};
export type RoleListResponse = {
items: RoleSummary[];
total: number;
};
export type RPUsageDailyMetric = {
date: string;
tenantId: string;
tenantType: string;
tenantName?: string;
clientId: string;
clientName: string;
loginRequests: number;
otherRequests: number;
uniqueSubjects: number;
};
export type RPUsagePeriod = "day" | "week" | "month";
export type RPUsageDailyResponse = {
items: RPUsageDailyMetric[];
days: number;
period: RPUsagePeriod;
tenantId?: string;
};
export type AdminOverviewStats = {
totalTenants: number;
totalUsers: number;
oidcClients: number;
auditEvents24h: number;
};
export type IdentityCacheStatus = {
status: string;
redisReady: boolean;
mirrorVersion?: string;
observedCount: number;
keyCount: number;
lastRefreshedAt?: string;
lastError?: string;
updatedAt?: string;
};
export type OrySSOTSystemStatus = {
identityCache: IdentityCacheStatus;
};
export type IdentityCacheFlushResult = {
status: string;
flushedKeys: number;
updatedAt: string;
};
export type DataIntegrityStatus = "pass" | "warning" | "fail";
export type DataIntegrityCheck = {
key: string;
label: string;
description: string;
status: DataIntegrityStatus;
severity: "info" | "warning" | "error" | string;
count: number;
};
export type DataIntegritySection = {
key: string;
label: string;
status: DataIntegrityStatus;
checks: DataIntegrityCheck[];
};
export type DataIntegrityReport = {
status: DataIntegrityStatus;
checkedAt: string;
summary: {
totalChecks: number;
passed: number;
warnings: number;
failures: number;
};
sections: DataIntegritySection[];
};
export type OrphanUserLoginID = {
id: string;
userId: string;
userEmail?: string;
userDeletedAt?: string;
tenantId: string;
tenantSlug?: string;
tenantDeletedAt?: string;
fieldKey: string;
loginId: string;
reasons: string[];
};
export type OrphanUserLoginIDListResponse = {
items: OrphanUserLoginID[];
total: number;
};
export type DeleteOrphanUserLoginIDsResult = {
deletedCount: number;
deleted: OrphanUserLoginID[];
skippedIds: string[];
};
export async function fetchAuditLogs(
limit = 50,
cursor?: string,
search?: string,
status?: string,
) {
const { data } = await apiClient.get<AuditLogListResponse>("/v1/audit", {
params: { limit, cursor, search, status },
});
return data;
}
export async function fetchAdminOverviewStats() {
const { data } = await apiClient.get<AdminOverviewStats>("/v1/admin/stats");
return data;
}
export async function fetchDataIntegrityReport() {
const { data } = await apiClient.get<DataIntegrityReport>(
"/v1/admin/integrity",
);
return data;
}
export async function fetchOrphanUserLoginIDs() {
const { data } = await apiClient.get<OrphanUserLoginIDListResponse>(
"/v1/admin/integrity/orphan-user-login-ids",
);
return data;
}
export async function deleteOrphanUserLoginIDs(ids: string[]) {
const { data } = await apiClient.delete<DeleteOrphanUserLoginIDsResult>(
"/v1/admin/integrity/orphan-user-login-ids",
{ data: { ids } },
);
return data;
}
export async function fetchOrySSOTSystemStatus() {
const { data } =
await apiClient.get<OrySSOTSystemStatus>("/v1/admin/ory/ssot");
return data;
}
export async function flushIdentityCache() {
const { data } = await apiClient.post<IdentityCacheFlushResult>(
"/v1/admin/ory/ssot/identity-cache/flush",
);
return data;
}
export async function fetchAdminRPUsageDaily({
days = 14,
period = "day",
tenantId,
}: {
days?: number;
period?: RPUsagePeriod;
tenantId?: string;
} = {}) {
const { data } = await apiClient.get<RPUsageDailyResponse>(
"/v1/admin/rp-usage/daily",
{
params: { days, period, tenantId: tenantId || undefined },
},
);
return data;
}
export async function fetchTenants(
limit = 50,
offset = 0,
parentId?: string,
cursor?: string,
search?: string,
) {
const { data } = await apiClient.get<TenantListResponse>(
"/v1/admin/tenants",
{
params: { limit, offset, parentId, cursor, search },
},
);
return data;
}
function getAdminApiBaseUrl() {
if (
(window as Window & typeof globalThis & { _IS_TEST_MODE?: boolean })
._IS_TEST_MODE
) {
return "http://playwright-mock/api";
}
return import.meta.env.VITE_ADMIN_API_BASE ?? "/api";
}
async function buildAdminRequestHeaders() {
const headers: Record<string, string> = {};
const user = await userManager.getUser();
const sessionToken =
user?.access_token || window.localStorage.getItem("admin_session");
if (sessionToken) {
headers.Authorization = `Bearer ${sessionToken}`;
}
const tenantId = window.localStorage.getItem("admin_tenant");
if (tenantId) {
headers["X-Tenant-ID"] = tenantId;
}
const isMockRoleEnabled =
window.localStorage.getItem("X-Mock-Role-Enabled") === "true";
const mockRole = window.localStorage.getItem("X-Mock-Role");
if (isMockRoleEnabled && mockRole) {
headers["X-Test-Role"] = mockRole;
}
return headers;
}
export async function fetchAllTenants({
pageSize = 100,
parentId,
}: {
pageSize?: number;
parentId?: string;
} = {}) {
return fetchAllCursorPages<TenantSummary>({
baseUrl: getAdminApiBaseUrl(),
path: "/v1/admin/tenants",
pageSize,
params: { parentId },
headers: await buildAdminRequestHeaders(),
}) as Promise<TenantListResponse>;
}
export async function fetchTenant(tenantId: string) {
const { data } = await apiClient.get<TenantSummary>(
`/v1/admin/tenants/${tenantId}`,
);
return data;
}
export async function createTenant(payload: TenantCreateRequest) {
const { data } = await apiClient.post<TenantSummary>(
"/v1/admin/tenants",
payload,
);
return data;
}
export async function updateTenant(
tenantId: string,
payload: TenantUpdateRequest,
) {
const { data } = await apiClient.put<TenantSummary>(
`/v1/admin/tenants/${tenantId}`,
payload,
);
return data;
}
export async function deleteTenant(tenantId: string) {
await apiClient.delete(`/v1/admin/tenants/${tenantId}`);
}
export async function deleteTenantsBulk(ids: string[]) {
await apiClient.delete("/v1/admin/tenants/bulk", {
data: { ids },
});
}
export async function exportTenantsCSV(includeIds = false, parentId?: string) {
const response = await apiClient.get<Blob>("/v1/admin/tenants/export", {
params: { includeIds, parentId: parentId || undefined },
responseType: "blob",
});
const dispositionHeader = response.headers["content-disposition"];
const disposition = Array.isArray(dispositionHeader)
? dispositionHeader[0]
: String(dispositionHeader ?? "");
const filenameMatch = disposition?.match(/filename="?([^"]+)"?/i);
return {
blob: response.data,
filename: filenameMatch?.[1] ?? "tenants.csv",
};
}
export async function importTenantsCSV(file: File) {
const formData = new FormData();
formData.append("file", file);
const { data } = await apiClient.post<TenantImportResult>(
"/v1/admin/tenants/import",
formData,
{
headers: {
"Content-Type": "multipart/form-data",
},
},
);
return data;
}
export async function approveTenant(tenantId: string) {
const { data } = await apiClient.post<TenantSummary>(
`/v1/admin/tenants/${tenantId}/approve`,
);
return data;
}
export type TenantAdmin = {
id: string;
name: string;
email: string;
};
export async function fetchTenantAdmins(tenantId: string) {
const { data } = await apiClient.get<TenantAdmin[]>(
`/v1/admin/tenants/${tenantId}/admins`,
);
return data;
}
export async function addTenantAdmin(tenantId: string, userId: string) {
await apiClient.post(`/v1/admin/tenants/${tenantId}/admins/${userId}`);
}
export async function removeTenantAdmin(tenantId: string, userId: string) {
await apiClient.delete(`/v1/admin/tenants/${tenantId}/admins/${userId}`);
}
export async function fetchTenantOwners(tenantId: string) {
const { data } = await apiClient.get<TenantAdmin[]>(
`/v1/admin/tenants/${tenantId}/owners`,
);
return data;
}
export async function addTenantOwner(tenantId: string, userId: string) {
await apiClient.post(`/v1/admin/tenants/${tenantId}/owners/${userId}`);
}
export async function removeTenantOwner(tenantId: string, userId: string) {
await apiClient.delete(`/v1/admin/tenants/${tenantId}/owners/${userId}`);
}
export type TenantRelation = {
userId: string;
name: string;
email: string;
relations: string[];
};
export async function fetchTenantRelations(tenantId: string) {
const { data } = await apiClient.get<{ items: TenantRelation[] }>(
`/v1/admin/tenants/${tenantId}/relations`,
);
return data.items;
}
export async function addTenantRelation(
tenantId: string,
userId: string,
relation: string,
) {
await apiClient.post(`/v1/admin/tenants/${tenantId}/relations`, {
userId,
relation,
});
}
export async function removeTenantRelation(
tenantId: string,
userId: string,
relation: string,
) {
await apiClient.delete(`/v1/admin/tenants/${tenantId}/relations`, {
data: { userId, relation },
});
}
export async function fetchSystemRelations() {
const { data } = await apiClient.get<{ items: TenantRelation[] }>(
`/v1/admin/system/relations`,
);
return data.items;
}
export async function addSystemRelation(userId: string, relation: string) {
await apiClient.post(`/v1/admin/system/relations`, {
userId,
relation,
});
}
export async function removeSystemRelation(userId: string, relation: string) {
await apiClient.delete(`/v1/admin/system/relations`, {
data: { userId, relation },
});
}
// Group Management
export type GroupMember = {
id: string;
name: string;
email: string;
};
export type GroupSummary = {
id: string;
tenantId: string;
parentId?: string;
name: string;
description?: string;
unitType?: string;
members?: GroupMember[];
createdAt?: string;
updatedAt?: string;
};
export type GroupCreateRequest = {
name: string;
parentId?: string;
description?: string;
unitType?: string;
};
export async function fetchGroups(tenantId: string) {
const { data } = await apiClient.get<GroupSummary[]>(
`/v1/admin/tenants/${tenantId}/organization`,
);
return data;
}
export async function fetchGroup(tenantId: string, groupId: string) {
const { data } = await apiClient.get<GroupSummary>(
`/v1/admin/tenants/${tenantId}/organization/${groupId}`,
);
return data;
}
export async function createGroup(
tenantId: string,
payload: GroupCreateRequest,
) {
const { data } = await apiClient.post<GroupSummary>(
`/v1/admin/tenants/${tenantId}/organization`,
payload,
);
return data;
}
export async function deleteGroup(tenantId: string, groupId: string) {
await apiClient.delete(
`/v1/admin/tenants/${tenantId}/organization/${groupId}`,
);
}
export async function addGroupMember(
tenantId: string,
groupId: string,
userId: string,
) {
await apiClient.post(
`/v1/admin/tenants/${tenantId}/organization/${groupId}/members`,
{ userId },
);
}
export async function removeGroupMember(
tenantId: string,
groupId: string,
userId: string,
) {
await apiClient.delete(
`/v1/admin/tenants/${tenantId}/organization/${groupId}/members/${userId}`,
);
}
export type GroupRole = {
tenantId: string;
tenantName: string;
relation: string;
};
export async function fetchGroupRoles(tenantId: string, groupId: string) {
const { data } = await apiClient.get<GroupRole[]>(
`/v1/admin/tenants/${tenantId}/organization/${groupId}/roles`,
);
return data;
}
export async function assignGroupRole(
tenantId: string,
groupId: string,
targetTenantId: string,
relation: string,
) {
await apiClient.post(
`/v1/admin/tenants/${tenantId}/organization/${groupId}/roles`,
{ tenantId: targetTenantId, relation },
);
}
export async function removeGroupRole(
tenantId: string,
groupId: string,
targetTenantId: string,
relation: string,
) {
await apiClient.delete(
`/v1/admin/tenants/${tenantId}/organization/${groupId}/roles/${targetTenantId}/${relation}`,
);
}
// API Key Management (M2M)
export type ApiKeyCreateRequest = {
name: string;
scopes: string[];
};
export type ApiKeyCreateResponse = {
apiKey: ApiKeySummary;
clientSecret: string;
};
export type ApiKeyUpdateScopesRequest = {
scopes: string[];
};
export async function fetchApiKeys(limit = 50, offset = 0) {
const { data } = await apiClient.get<ApiKeyListResponse>(
"/v1/admin/api-keys",
{
params: { limit, offset },
},
);
return data;
}
export async function createApiKey(payload: ApiKeyCreateRequest) {
const { data } = await apiClient.post<ApiKeyCreateResponse>(
"/v1/admin/api-keys",
payload,
);
return data;
}
export async function updateApiKeyScopes(
apiKeyId: string,
payload: ApiKeyUpdateScopesRequest,
) {
const { data } = await apiClient.patch<ApiKeySummary>(
`/v1/admin/api-keys/${apiKeyId}`,
payload,
);
return data;
}
export async function rotateApiKeySecret(apiKeyId: string) {
const { data } = await apiClient.post<ApiKeyCreateResponse>(
`/v1/admin/api-keys/${apiKeyId}/secret/rotate`,
);
return data;
}
export async function deleteApiKey(apiKeyId: string) {
await apiClient.delete(`/v1/admin/api-keys/${apiKeyId}`);
}
// User Management
export type UserSummary = {
id: string;
email: string;
loginId?: string;
name: string;
phone?: string;
role: string;
status: string;
tenantSlug?: string;
companyCode?: string;
tenant?: TenantSummary;
joinedTenants?: TenantSummary[]; // [New] 다중 소속 테넌트 목록
metadata?: Record<string, unknown>;
department?: string;
grade?: string;
position?: string;
jobTitle?: string;
createdAt: string;
updatedAt: string;
};
export type UserListResponse = {
items: UserSummary[];
limit: number;
offset: number;
total: number;
next_cursor?: string;
nextCursor?: string;
};
export type UserCreateRequest = {
email: string;
loginId?: string;
password?: string;
name: string;
phone?: string;
role?: string;
tenantSlug?: string;
department?: string;
grade?: string;
position?: string;
jobTitle?: string;
primaryTenantId?: string;
primaryTenantName?: string;
primaryTenantIsOwner?: boolean;
additionalAppointments?: UserAppointment[];
metadata?: Record<string, unknown>;
};
export type UserCreateResponse = UserSummary & {
initialPassword?: string;
};
export type UserUpdateRequest = {
email?: string;
loginId?: string;
password?: string;
name?: string;
phone?: string;
role?: string;
status?: string;
tenantSlug?: string;
isPrimaryTenant?: boolean;
isAddTenant?: boolean;
isRemoveTenant?: boolean;
department?: string;
grade?: string;
position?: string;
jobTitle?: string;
primaryTenantId?: string;
primaryTenantName?: string;
primaryTenantIsOwner?: boolean;
additionalAppointments?: UserAppointment[];
metadata?: Record<string, unknown>;
};
export type GlobalCustomClaimPermission = "admin_only" | "user_and_admin";
export type GlobalCustomClaimDefinition = {
key: string;
label: string;
valueType:
| "text"
| "number"
| "boolean"
| "array"
| "object"
| "date"
| "datetime";
readPermission: GlobalCustomClaimPermission;
writePermission: GlobalCustomClaimPermission;
description?: string;
};
export type GlobalCustomClaimDefinitionsResponse = {
items: GlobalCustomClaimDefinition[];
};
export type UserAppointment = {
tenantId: string;
tenantSlug?: string;
tenantName: string;
isPrimary?: boolean;
isOwner?: boolean;
isAdmin?: boolean;
isManager?: boolean;
jobTitle?: string;
grade?: string;
position?: string;
};
export type BulkUserAppointment = {
tenantId?: string;
tenantSlug?: string;
tenantName?: string;
isPrimary?: boolean;
isOwner?: boolean;
isAdmin?: boolean;
isManager?: boolean;
department?: string;
grade?: string;
position?: string;
jobTitle?: string;
metadata?: Record<string, unknown>;
};
export type BulkUserItem = {
email: string;
loginId?: string;
name: string;
phone?: string;
role?: string;
tenantId?: string;
tenantSlug?: string;
emailDomain?: string;
department?: string;
grade?: string;
position?: string;
jobTitle?: string;
additionalAppointments?: BulkUserAppointment[];
tenantImport?: {
sourceTenantId?: string;
slug?: string;
name?: string;
type?: string;
parentTenantId?: string;
parentTenantSlug?: string;
parentTenantName?: string;
memo?: string;
emailDomain?: string;
};
metadata: Record<string, unknown>;
importErrors?: string[];
};
export type BulkUserResult = {
email: string;
originalEmail?: string;
suggestedEmail?: string;
status?: string;
warnings?: string[];
success: boolean;
message?: string;
userId?: string;
modifiedFields?: string[];
};
export type BulkUserResponse = {
results: BulkUserResult[];
};
export type WorksmobileOutboxItem = {
id: string;
resourceType: string;
resourceId: string;
action: string;
payload?: Record<string, unknown>;
status: string;
retryCount: number;
lastError?: string;
createdAt: string;
updatedAt: string;
};
export type WorksmobileOverview = {
tenant: TenantSummary;
config: {
enabled: boolean;
domainMappings?: Record<string, number>;
tokenConfigured: boolean;
adminTenantId?: string;
};
recentJobs: WorksmobileOutboxItem[];
};
export type WorksmobileCredentialBatch = {
batchId: string;
operation?: string;
userCount: number;
pendingCount?: number;
processingCount?: number;
processedCount?: number;
failedCount?: number;
hasPasswords: boolean;
deletedAt?: string;
failures?: WorksmobileCredentialBatchFailure[];
createdAt?: string;
updatedAt?: string;
};
export type WorksmobileCredentialBatchFailure = {
userId?: string;
email?: string;
status: string;
retryCount: number;
lastError?: string;
updatedAt?: string;
};
export type WorksmobilePendingJobDeleteResult = {
deletedCount: number;
};
export type WorksmobileComparisonItem = {
resourceType: string;
baronId?: string;
baronSlug?: string;
baronName?: string;
baronEmail?: string;
baronPhone?: string;
baronEmployeeNumber?: string;
baronPrimaryOrgId?: string;
baronPrimaryOrgSlug?: string;
baronPrimaryOrgName?: string;
baronParentId?: string;
baronParentSlug?: string;
baronParentName?: string;
worksmobileId?: string;
externalKey?: string;
worksmobileName?: string;
worksmobileEmail?: string;
worksmobilePhone?: string;
worksmobileEmployeeNumber?: string;
worksmobileAccountStatus?: string;
worksmobileLevelId?: string;
worksmobileLevelName?: string;
worksmobileTask?: string;
worksmobileDomainId?: number;
worksmobileDomainName?: string;
worksmobilePrimaryOrgId?: string;
worksmobilePrimaryOrgName?: string;
worksmobilePrimaryOrgPositionId?: string;
worksmobilePrimaryOrgPositionName?: string;
worksmobilePrimaryOrgIsManager?: boolean;
baronParentWorksmobileId?: string;
baronParentWorksmobileName?: string;
baronParentWorksmobileEmail?: string;
worksmobileParentId?: string;
worksmobileParentName?: string;
worksmobileParentEmail?: string;
worksmobileParentExternalKey?: string;
worksmobileJobStatus?: string;
worksmobileJobRetryCount?: number;
worksmobileLastError?: string;
worksmobileLastAttemptAt?: string;
updateReasons?: string[];
status: string;
};
export type WorksmobileComparison = {
users: WorksmobileComparisonItem[];
groups: WorksmobileComparisonItem[];
};
export async function fetchUsers(
limit = 50,
offset = 0,
search?: string,
tenantSlug?: string,
cursor?: string,
) {
const { data } = await apiClient.get<UserListResponse>("/v1/admin/users", {
params: { limit, offset, search, tenantSlug, cursor },
});
return data;
}
export async function fetchUser(userId: string) {
const { data } = await apiClient.get<UserSummary>(
`/v1/admin/users/${userId}`,
);
return data;
}
export async function fetchGlobalCustomClaimDefinitions() {
const { data } = await apiClient.get<GlobalCustomClaimDefinitionsResponse>(
"/v1/admin/global-custom-claims",
);
return data;
}
export async function updateGlobalCustomClaimDefinitions(
payload: GlobalCustomClaimDefinitionsResponse,
) {
const { data } = await apiClient.put<GlobalCustomClaimDefinitionsResponse>(
"/v1/admin/global-custom-claims",
payload,
);
return data;
}
export async function createUser(payload: UserCreateRequest) {
const { data } = await apiClient.post<UserCreateResponse>(
"/v1/admin/users",
payload,
);
return data;
}
export async function exportUsersCSV(
search?: string,
tenantSlug?: string,
includeIds = false,
) {
const response = await apiClient.get<Blob>("/v1/admin/users/export", {
params: { search, tenantSlug, includeIds },
responseType: "blob",
});
const dispositionHeader = response.headers["content-disposition"];
const disposition = Array.isArray(dispositionHeader)
? dispositionHeader[0]
: String(dispositionHeader ?? "");
const filenameMatch = disposition?.match(/filename="?([^"]+)"?/i);
return {
blob: response.data,
filename: filenameMatch?.[1] ?? "users.csv",
};
}
export async function bulkCreateUsers(users: BulkUserItem[]) {
const { data } = await apiClient.post<BulkUserResponse>(
"/v1/admin/users/bulk",
{ users },
);
return data;
}
export async function fetchWorksmobileOverview(tenantId: string) {
const { data } = await apiClient.get<WorksmobileOverview>(
`/v1/admin/tenants/${tenantId}/worksmobile`,
);
return data;
}
export async function fetchWorksmobileComparison(
tenantId: string,
includeMatched = false,
) {
const { data } = await apiClient.get<WorksmobileComparison>(
`/v1/admin/tenants/${tenantId}/worksmobile/comparison`,
{
params: { includeMatched },
},
);
return data;
}
export async function fetchWorksmobileCredentialBatches(tenantId: string) {
const { data } = await apiClient.get<WorksmobileCredentialBatch[]>(
`/v1/admin/tenants/${tenantId}/worksmobile/credential-batches`,
);
return data;
}
export async function downloadWorksmobileInitialPasswordsCSV(
tenantId: string,
batchId?: string,
) {
const trimmedBatchId = batchId?.trim();
const response = await apiClient.get<Blob>(
`/v1/admin/tenants/${tenantId}/worksmobile/initial-passwords.csv`,
{
...(trimmedBatchId ? { params: { batchId: trimmedBatchId } } : {}),
responseType: "blob",
},
);
const dispositionHeader = response.headers["content-disposition"];
const disposition = Array.isArray(dispositionHeader)
? dispositionHeader[0]
: String(dispositionHeader ?? "");
const filenameMatch = disposition?.match(/filename="?([^"]+)"?/i);
return {
blob: response.data,
filename: filenameMatch?.[1] ?? "worksmobile_initial_passwords.csv",
};
}
export async function deleteWorksmobileCredentialBatchPasswords(
tenantId: string,
batchId: string,
) {
const { data } = await apiClient.delete<WorksmobileCredentialBatch>(
`/v1/admin/tenants/${tenantId}/worksmobile/credential-batches/${encodeURIComponent(batchId)}/passwords`,
);
return data;
}
export async function deleteWorksmobilePendingJobs(tenantId: string) {
const { data } = await apiClient.delete<WorksmobilePendingJobDeleteResult>(
`/v1/admin/tenants/${tenantId}/worksmobile/jobs/pending`,
);
return data;
}
export async function enqueueWorksmobileBackfillDryRun(tenantId: string) {
const { data } = await apiClient.post(
`/v1/admin/tenants/${tenantId}/worksmobile/backfill/dry-run`,
);
return data;
}
export async function enqueueWorksmobileOrgUnitSync(
tenantId: string,
orgUnitId: string,
) {
const { data } = await apiClient.post<WorksmobileOutboxItem>(
`/v1/admin/tenants/${tenantId}/worksmobile/orgunits/${encodeURIComponent(orgUnitId)}/sync`,
);
return data;
}
export async function enqueueWorksmobileOrgUnitDelete(
tenantId: string,
orgUnitId: string,
) {
const { data } = await apiClient.post<WorksmobileOutboxItem>(
`/v1/admin/tenants/${tenantId}/worksmobile/orgunits/${encodeURIComponent(orgUnitId)}/delete`,
);
return data;
}
export async function enqueueWorksmobileUserSync(
tenantId: string,
userId: string,
credentialBatchId?: string,
initialPassword?: string,
) {
const trimmedBatchId = credentialBatchId?.trim();
const trimmedInitialPassword = initialPassword?.trim();
const path = `/v1/admin/tenants/${tenantId}/worksmobile/users/${userId}/sync`;
const body = {
...(trimmedBatchId ? { credentialBatchId: trimmedBatchId } : {}),
...(trimmedInitialPassword
? { initialPassword: trimmedInitialPassword }
: {}),
};
const { data } =
Object.keys(body).length > 0
? await apiClient.post<WorksmobileOutboxItem>(path, body)
: await apiClient.post<WorksmobileOutboxItem>(path);
return data;
}
export async function resetWorksmobileUserPassword(
tenantId: string,
userId: string,
credentialBatchId?: string,
) {
const trimmedBatchId = credentialBatchId?.trim();
const path = `/v1/admin/tenants/${tenantId}/worksmobile/users/${userId}/password/reset`;
const { data } = trimmedBatchId
? await apiClient.post<WorksmobileOutboxItem>(path, {
credentialBatchId: trimmedBatchId,
})
: await apiClient.post<WorksmobileOutboxItem>(path);
return data;
}
export async function retryWorksmobileJob(tenantId: string, jobId: string) {
const { data } = await apiClient.post(
`/v1/admin/tenants/${tenantId}/worksmobile/jobs/${jobId}/retry`,
);
return data;
}
export async function bulkUpdateUsers(payload: {
userIds: string[];
status?: string;
role?: string;
tenantSlug?: string;
isPrimaryTenant?: boolean;
isAddTenant?: boolean;
department?: string;
position?: string;
grade?: string;
jobTitle?: string;
}) {
const { data } = await apiClient.put<BulkUserResponse>(
"/v1/admin/users/bulk",
payload,
);
return data;
}
export async function bulkDeleteUsers(userIds: string[]) {
const { data } = await apiClient.delete("/v1/admin/users/bulk", {
data: { userIds },
});
return data;
}
export async function updateUser(userId: string, payload: UserUpdateRequest) {
const { data } = await apiClient.put<UserSummary>(
`/v1/admin/users/${userId}`,
payload,
);
return data;
}
export type PasswordPolicyResponse = {
minLength?: number;
lowercase?: boolean;
uppercase?: boolean;
number?: boolean;
nonAlphanumeric?: boolean;
minCharacterTypes?: number;
};
export async function fetchPasswordPolicy() {
const { data } = await apiClient.get<PasswordPolicyResponse>(
"/v1/auth/password/policy",
);
return data;
}
export async function deleteUser(userId: string) {
await apiClient.delete(`/v1/admin/users/${userId}`);
}
export type UserRpHistoryItem = {
client_id: string;
client_name: string;
lastLoginAt: string;
status: string;
};
export async function fetchUserRpHistory(userId: string) {
const { data } = await apiClient.get<UserRpHistoryItem[]>(
`/v1/admin/users/${userId}/rp-history`,
);
return data;
}
export type SystemPermissions = {
overview: boolean;
tenants: boolean;
org_chart: boolean;
worksmobile: boolean;
ory_ssot: boolean;
data_integrity: boolean;
users: boolean;
permissions_direct: boolean;
auth_guard: boolean;
api_keys: boolean;
audit_logs: boolean;
manage_overview?: boolean;
manage_tenants?: boolean;
manage_org_chart?: boolean;
manage_worksmobile?: boolean;
manage_ory_ssot?: boolean;
manage_data_integrity?: boolean;
manage_users?: boolean;
manage_permissions_direct?: boolean;
manage_auth_guard?: boolean;
manage_api_keys?: boolean;
manage_audit_logs?: boolean;
};
export type UserProfileResponse = {
id: string;
email: string;
name: string;
phone: string;
role: string;
department: string;
affiliationType: string;
tenantSlug?: string;
tenantId?: string;
metadata?: Record<string, unknown>;
tenant?: TenantSummary;
manageableTenants?: TenantSummary[];
systemPermissions?: SystemPermissions;
};
export async function fetchMe() {
const { data } = await apiClient.get<UserProfileResponse>("/v1/user/me");
return data;
}
// Relying Party Management
export type RelyingParty = {
clientId: string;
tenantId: string;
name: string;
description: string;
createdAt: string;
updatedAt: string;
};
export type HydraClientReq = {
client_id?: string;
client_name: string;
client_secret?: string;
redirect_uris: string[];
scope?: string;
token_endpoint_auth_method?: string;
grant_types?: string[];
response_types?: string[];
metadata?: Record<string, unknown>;
};
export async function fetchRelyingParties(tenantId: string) {
const { data } = await apiClient.get<RelyingParty[]>(
`/v1/admin/tenants/${tenantId}/relying-parties`,
);
return data;
}
export async function fetchAllRelyingParties() {
const { data } = await apiClient.get<RelyingParty[]>(
"/v1/admin/relying-parties",
);
return data;
}
export async function createRelyingParty(
tenantId: string,
payload: HydraClientReq,
) {
const { data } = await apiClient.post<RelyingParty>(
`/v1/admin/tenants/${tenantId}/relying-parties`,
payload,
);
return data;
}
export async function fetchRelyingParty(id: string) {
const { data } = await apiClient.get<{
relyingParty: RelyingParty;
oauth2Config: HydraClientReq;
}>(`/v1/admin/relying-parties/${id}`);
return data;
}
export async function updateRelyingParty(id: string, payload: HydraClientReq) {
const { data } = await apiClient.put<RelyingParty>(
`/v1/admin/relying-parties/${id}`,
payload,
);
return data;
}
export async function deleteRelyingParty(id: string) {
await apiClient.delete(`/v1/admin/relying-parties/${id}`);
}
export type RPOwner = {
subject: string;
name?: string;
email?: string;
type: string;
};
export async function fetchRPOwners(clientId: string) {
const { data } = await apiClient.get<RPOwner[]>(
`/v1/admin/relying-parties/${clientId}/owners`,
);
return data;
}
export async function addRPOwner(clientId: string, subject: string) {
await apiClient.post(
`/v1/admin/relying-parties/${clientId}/owners/${subject}`,
);
}
export async function removeRPOwner(clientId: string, subject: string) {
await apiClient.delete(
`/v1/admin/relying-parties/${clientId}/owners/${subject}`,
);
}