1
0
forked from baron/baron-sso
Files
baron-sso/adminfront/src/lib/adminApi.ts

973 lines
22 KiB
TypeScript

import apiClient from "./apiClient";
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; // Added member count
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;
};
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 TenantImportResult = {
created: number;
updated: number;
failed: number;
errors: string[];
};
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;
oidcClients: number;
auditEvents24h: number;
};
export type UserProjectionStatus = {
name: string;
status: "ready" | "failed" | "syncing" | string;
ready: boolean;
lastSyncedAt?: string;
lastError?: string;
updatedAt?: string;
projectedUsers: number;
};
export type UserProjectionActionResult = {
status: string;
syncedUsers: number;
updatedAt: string;
};
export async function fetchAuditLogs(limit = 50, cursor?: string) {
const { data } = await apiClient.get<AuditLogListResponse>("/v1/audit", {
params: { limit, cursor },
});
return data;
}
export async function fetchAdminOverviewStats() {
const { data } = await apiClient.get<AdminOverviewStats>("/v1/admin/stats");
return data;
}
export async function fetchUserProjectionStatus() {
const { data } = await apiClient.get<UserProjectionStatus>(
"/v1/admin/projections/users",
);
return data;
}
export async function reconcileUserProjection() {
const { data } = await apiClient.post<UserProjectionActionResult>(
"/v1/admin/projections/users/reconcile",
);
return data;
}
export async function resetUserProjection() {
const { data } = await apiClient.post<UserProjectionActionResult>(
"/v1/admin/projections/users/reset",
);
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) {
const { data } = await apiClient.get<TenantListResponse>(
"/v1/admin/tenants",
{
params: { limit, offset, parentId },
},
);
return data;
}
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) {
const response = await apiClient.get<Blob>("/v1/admin/tenants/export", {
params: { 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] ?? "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}`);
}
// 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 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 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;
position?: string;
jobTitle?: string;
createdAt: string;
updatedAt: string;
};
export type UserListResponse = {
items: UserSummary[];
limit: number;
offset: number;
total: number;
};
export type UserCreateRequest = {
email: string;
loginId?: string;
password?: string;
name: string;
phone?: string;
role?: string;
tenantSlug?: string;
department?: 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 = {
loginId?: string;
password?: string;
name?: string;
phone?: string;
role?: string;
status?: string;
tenantSlug?: string;
isAddTenant?: boolean;
isRemoveTenant?: boolean;
department?: string;
position?: string;
jobTitle?: string;
primaryTenantId?: string;
primaryTenantName?: string;
primaryTenantIsOwner?: boolean;
additionalAppointments?: UserAppointment[];
metadata?: Record<string, unknown>;
};
export type UserAppointment = {
tenantId: string;
tenantSlug?: string;
tenantName: string;
isPrimary?: boolean;
isOwner: boolean;
jobTitle?: string;
position?: string;
};
export type BulkUserItem = {
email: string;
loginId?: string;
name: string;
phone?: string;
role?: string;
tenantSlug?: string;
department?: string;
position?: string;
jobTitle?: string;
tenantImport?: {
sourceTenantId?: string;
slug?: string;
name?: string;
type?: string;
parentTenantId?: string;
parentTenantSlug?: string;
parentTenantName?: string;
memo?: string;
emailDomain?: string;
};
metadata: Record<string, string>;
};
export type BulkUserResult = {
email: string;
originalEmail?: string;
suggestedEmail?: string;
status?: string;
warnings?: string[];
success: boolean;
message?: string;
userId?: string;
};
export type BulkUserResponse = {
results: BulkUserResult[];
};
export type WorksmobileOutboxItem = {
id: string;
resourceType: string;
resourceId: string;
action: string;
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 WorksmobileComparisonItem = {
resourceType: string;
baronId?: string;
baronName?: string;
baronEmail?: string;
baronPrimaryOrgId?: string;
baronPrimaryOrgName?: string;
baronParentId?: string;
baronParentName?: string;
worksmobileId?: string;
externalKey?: string;
worksmobileName?: string;
worksmobileEmail?: string;
worksmobileLevelId?: string;
worksmobileLevelName?: string;
worksmobileTask?: string;
worksmobileDomainId?: number;
worksmobileDomainName?: string;
worksmobilePrimaryOrgId?: string;
worksmobilePrimaryOrgName?: string;
worksmobilePrimaryOrgPositionId?: string;
worksmobilePrimaryOrgPositionName?: string;
worksmobilePrimaryOrgIsManager?: boolean;
worksmobileParentId?: string;
worksmobileParentName?: string;
status: string;
};
export type WorksmobileComparison = {
users: WorksmobileComparisonItem[];
groups: WorksmobileComparisonItem[];
};
export async function fetchUsers(
limit = 50,
offset = 0,
search?: string,
tenantSlug?: string,
) {
const { data } = await apiClient.get<UserListResponse>("/v1/admin/users", {
params: { limit, offset, search, tenantSlug },
});
return data;
}
export async function fetchUser(userId: string) {
const { data } = await apiClient.get<UserSummary>(
`/v1/admin/users/${userId}`,
);
return data;
}
export async function createUser(payload: UserCreateRequest) {
// Map tenantSlug to companyCode for backend compatibility
const requestPayload: UserCreateRequest & { companyCode?: string } = {
...payload,
};
if (payload.tenantSlug !== undefined) {
requestPayload.companyCode = payload.tenantSlug;
}
const { data } = await apiClient.post<UserCreateResponse>(
"/v1/admin/users",
requestPayload,
);
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 mappedUsers = users.map((u) => {
const mapped: BulkUserItem & { companyCode?: string } = { ...u };
if (u.tenantSlug !== undefined) {
mapped.companyCode = u.tenantSlug;
}
return mapped;
});
const { data } = await apiClient.post<BulkUserResponse>(
"/v1/admin/users/bulk",
{ users: mappedUsers },
);
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 downloadWorksmobileInitialPasswordsCSV(tenantId: string) {
const response = await apiClient.get<Blob>(
`/v1/admin/tenants/${tenantId}/worksmobile/initial-passwords.csv`,
{
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 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/${orgUnitId}/sync`,
);
return data;
}
export async function enqueueWorksmobileUserSync(
tenantId: string,
userId: string,
) {
const { data } = await apiClient.post<WorksmobileOutboxItem>(
`/v1/admin/tenants/${tenantId}/worksmobile/users/${userId}/sync`,
);
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;
department?: string;
position?: string;
jobTitle?: string;
}) {
const requestPayload: typeof payload & { companyCode?: string } = {
...payload,
};
if (payload.tenantSlug !== undefined) {
requestPayload.companyCode = payload.tenantSlug;
}
const { data } = await apiClient.put("/v1/admin/users/bulk", requestPayload);
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 requestPayload: UserUpdateRequest & { companyCode?: string } = {
...payload,
};
if (payload.tenantSlug !== undefined) {
requestPayload.companyCode = payload.tenantSlug;
}
const { data } = await apiClient.put<UserSummary>(
`/v1/admin/users/${userId}`,
requestPayload,
);
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 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[];
};
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}`,
);
}