@tidecloak/ui-framework 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +377 -0
- package/dist/index.d.mts +2739 -0
- package/dist/index.d.ts +2739 -0
- package/dist/index.js +12869 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +12703 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +54 -0
- package/src/components/common/ActionButton.tsx +234 -0
- package/src/components/common/EmptyState.tsx +140 -0
- package/src/components/common/LoadingSkeleton.tsx +121 -0
- package/src/components/common/RefreshButton.tsx +127 -0
- package/src/components/common/StatusBadge.tsx +177 -0
- package/src/components/common/index.ts +31 -0
- package/src/components/data-table/DataTable.tsx +201 -0
- package/src/components/data-table/PaginatedTable.tsx +247 -0
- package/src/components/data-table/index.ts +2 -0
- package/src/components/dialogs/CollapsibleSection.tsx +184 -0
- package/src/components/dialogs/ConfirmDialog.tsx +264 -0
- package/src/components/dialogs/DetailDialog.tsx +228 -0
- package/src/components/dialogs/index.ts +3 -0
- package/src/components/index.ts +5 -0
- package/src/components/pages/base/ApprovalsPageBase.tsx +680 -0
- package/src/components/pages/base/LogsPageBase.tsx +581 -0
- package/src/components/pages/base/RolesPageBase.tsx +1470 -0
- package/src/components/pages/base/TemplatesPageBase.tsx +761 -0
- package/src/components/pages/base/UsersPageBase.tsx +843 -0
- package/src/components/pages/base/index.ts +58 -0
- package/src/components/pages/connected/ApprovalsPage.tsx +797 -0
- package/src/components/pages/connected/LogsPage.tsx +267 -0
- package/src/components/pages/connected/RolesPage.tsx +525 -0
- package/src/components/pages/connected/TemplatesPage.tsx +181 -0
- package/src/components/pages/connected/UsersPage.tsx +237 -0
- package/src/components/pages/connected/index.ts +36 -0
- package/src/components/pages/index.ts +5 -0
- package/src/components/tabs/TabsView.tsx +300 -0
- package/src/components/tabs/index.ts +1 -0
- package/src/components/ui/index.tsx +1001 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useAutoRefresh.ts +119 -0
- package/src/hooks/usePagination.ts +152 -0
- package/src/hooks/useSelection.ts +81 -0
- package/src/index.ts +256 -0
- package/src/theme.ts +185 -0
- package/src/tide/index.ts +19 -0
- package/src/tide/tidePolicy.ts +270 -0
- package/src/types/index.ts +484 -0
- package/src/utils/index.ts +121 -0
package/src/theme.ts
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme constants for consistent styling across components.
|
|
3
|
+
* Use CSS variables for runtime theming, constants for TypeScript type safety.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Color palette
|
|
7
|
+
export const colors = {
|
|
8
|
+
// Backgrounds
|
|
9
|
+
background: {
|
|
10
|
+
primary: "var(--tc-bg-primary, #ffffff)",
|
|
11
|
+
secondary: "var(--tc-bg-secondary, #f9fafb)",
|
|
12
|
+
muted: "var(--tc-bg-muted, #f3f4f6)",
|
|
13
|
+
overlay: "var(--tc-bg-overlay, rgba(0, 0, 0, 0.5))",
|
|
14
|
+
},
|
|
15
|
+
// Foregrounds (text)
|
|
16
|
+
foreground: {
|
|
17
|
+
primary: "var(--tc-fg-primary, #111827)",
|
|
18
|
+
secondary: "var(--tc-fg-secondary, #374151)",
|
|
19
|
+
muted: "var(--tc-fg-muted, #6b7280)",
|
|
20
|
+
disabled: "var(--tc-fg-disabled, #9ca3af)",
|
|
21
|
+
},
|
|
22
|
+
// Borders
|
|
23
|
+
border: {
|
|
24
|
+
default: "var(--tc-border, #e5e7eb)",
|
|
25
|
+
focus: "var(--tc-border-focus, #3b82f6)",
|
|
26
|
+
error: "var(--tc-border-error, #dc2626)",
|
|
27
|
+
},
|
|
28
|
+
// Status colors
|
|
29
|
+
status: {
|
|
30
|
+
success: "var(--tc-success, #16a34a)",
|
|
31
|
+
successBg: "var(--tc-success-bg, #dcfce7)",
|
|
32
|
+
error: "var(--tc-error, #dc2626)",
|
|
33
|
+
errorBg: "var(--tc-error-bg, #fef2f2)",
|
|
34
|
+
warning: "var(--tc-warning, #f59e0b)",
|
|
35
|
+
warningBg: "var(--tc-warning-bg, #fef3c7)",
|
|
36
|
+
info: "var(--tc-info, #3b82f6)",
|
|
37
|
+
infoBg: "var(--tc-info-bg, #eff6ff)",
|
|
38
|
+
pending: "var(--tc-pending, #6b7280)",
|
|
39
|
+
pendingBg: "var(--tc-pending-bg, #f3f4f6)",
|
|
40
|
+
},
|
|
41
|
+
// Action colors
|
|
42
|
+
action: {
|
|
43
|
+
primary: "var(--tc-action-primary, #3b82f6)",
|
|
44
|
+
primaryHover: "var(--tc-action-primary-hover, #2563eb)",
|
|
45
|
+
danger: "var(--tc-action-danger, #dc2626)",
|
|
46
|
+
dangerHover: "var(--tc-action-danger-hover, #b91c1c)",
|
|
47
|
+
},
|
|
48
|
+
} as const;
|
|
49
|
+
|
|
50
|
+
// Spacing scale
|
|
51
|
+
export const spacing = {
|
|
52
|
+
xs: "0.25rem",
|
|
53
|
+
sm: "0.5rem",
|
|
54
|
+
md: "1rem",
|
|
55
|
+
lg: "1.5rem",
|
|
56
|
+
xl: "2rem",
|
|
57
|
+
"2xl": "3rem",
|
|
58
|
+
} as const;
|
|
59
|
+
|
|
60
|
+
// Border radius
|
|
61
|
+
export const radius = {
|
|
62
|
+
sm: "0.25rem",
|
|
63
|
+
md: "0.375rem",
|
|
64
|
+
lg: "0.5rem",
|
|
65
|
+
full: "9999px",
|
|
66
|
+
} as const;
|
|
67
|
+
|
|
68
|
+
// Font sizes
|
|
69
|
+
export const fontSize = {
|
|
70
|
+
xs: "0.75rem",
|
|
71
|
+
sm: "0.875rem",
|
|
72
|
+
base: "1rem",
|
|
73
|
+
lg: "1.125rem",
|
|
74
|
+
xl: "1.25rem",
|
|
75
|
+
"2xl": "1.5rem",
|
|
76
|
+
} as const;
|
|
77
|
+
|
|
78
|
+
// Z-index scale
|
|
79
|
+
export const zIndex = {
|
|
80
|
+
dropdown: 50,
|
|
81
|
+
sticky: 100,
|
|
82
|
+
modal: 200,
|
|
83
|
+
overlay: 300,
|
|
84
|
+
toast: 400,
|
|
85
|
+
} as const;
|
|
86
|
+
|
|
87
|
+
// Animation durations
|
|
88
|
+
export const duration = {
|
|
89
|
+
fast: "150ms",
|
|
90
|
+
normal: "200ms",
|
|
91
|
+
slow: "300ms",
|
|
92
|
+
} as const;
|
|
93
|
+
|
|
94
|
+
// Focus ring style (consistent across all interactive elements)
|
|
95
|
+
export const focusRing = {
|
|
96
|
+
outline: `2px solid ${colors.border.focus}`,
|
|
97
|
+
outlineOffset: "2px",
|
|
98
|
+
} as const;
|
|
99
|
+
|
|
100
|
+
// Shadow styles
|
|
101
|
+
export const shadow = {
|
|
102
|
+
sm: "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
|
|
103
|
+
md: "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
|
|
104
|
+
lg: "0 10px 15px -3px rgba(0, 0, 0, 0.1)",
|
|
105
|
+
} as const;
|
|
106
|
+
|
|
107
|
+
// CSS to inject for theming support
|
|
108
|
+
export const themeCSS = `
|
|
109
|
+
:root {
|
|
110
|
+
--tc-bg-primary: #ffffff;
|
|
111
|
+
--tc-bg-secondary: #f9fafb;
|
|
112
|
+
--tc-bg-muted: #f3f4f6;
|
|
113
|
+
--tc-bg-overlay: rgba(0, 0, 0, 0.5);
|
|
114
|
+
--tc-fg-primary: #111827;
|
|
115
|
+
--tc-fg-secondary: #374151;
|
|
116
|
+
--tc-fg-muted: #6b7280;
|
|
117
|
+
--tc-fg-disabled: #9ca3af;
|
|
118
|
+
--tc-border: #e5e7eb;
|
|
119
|
+
--tc-border-focus: #3b82f6;
|
|
120
|
+
--tc-border-error: #dc2626;
|
|
121
|
+
--tc-success: #16a34a;
|
|
122
|
+
--tc-success-bg: #dcfce7;
|
|
123
|
+
--tc-error: #dc2626;
|
|
124
|
+
--tc-error-bg: #fef2f2;
|
|
125
|
+
--tc-warning: #f59e0b;
|
|
126
|
+
--tc-warning-bg: #fef3c7;
|
|
127
|
+
--tc-info: #3b82f6;
|
|
128
|
+
--tc-info-bg: #eff6ff;
|
|
129
|
+
--tc-pending: #6b7280;
|
|
130
|
+
--tc-pending-bg: #f3f4f6;
|
|
131
|
+
--tc-action-primary: #3b82f6;
|
|
132
|
+
--tc-action-primary-hover: #2563eb;
|
|
133
|
+
--tc-action-danger: #dc2626;
|
|
134
|
+
--tc-action-danger-hover: #b91c1c;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@media (prefers-color-scheme: dark) {
|
|
138
|
+
:root {
|
|
139
|
+
--tc-bg-primary: #1f2937;
|
|
140
|
+
--tc-bg-secondary: #111827;
|
|
141
|
+
--tc-bg-muted: #374151;
|
|
142
|
+
--tc-bg-overlay: rgba(0, 0, 0, 0.7);
|
|
143
|
+
--tc-fg-primary: #f9fafb;
|
|
144
|
+
--tc-fg-secondary: #e5e7eb;
|
|
145
|
+
--tc-fg-muted: #9ca3af;
|
|
146
|
+
--tc-fg-disabled: #6b7280;
|
|
147
|
+
--tc-border: #374151;
|
|
148
|
+
--tc-border-focus: #60a5fa;
|
|
149
|
+
--tc-border-error: #f87171;
|
|
150
|
+
--tc-success: #22c55e;
|
|
151
|
+
--tc-success-bg: #14532d;
|
|
152
|
+
--tc-error: #f87171;
|
|
153
|
+
--tc-error-bg: #7f1d1d;
|
|
154
|
+
--tc-warning: #fbbf24;
|
|
155
|
+
--tc-warning-bg: #78350f;
|
|
156
|
+
--tc-info: #60a5fa;
|
|
157
|
+
--tc-info-bg: #1e3a8a;
|
|
158
|
+
--tc-pending: #9ca3af;
|
|
159
|
+
--tc-pending-bg: #374151;
|
|
160
|
+
--tc-action-primary: #60a5fa;
|
|
161
|
+
--tc-action-primary-hover: #3b82f6;
|
|
162
|
+
--tc-action-danger: #f87171;
|
|
163
|
+
--tc-action-danger-hover: #dc2626;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
@media (prefers-reduced-motion: reduce) {
|
|
168
|
+
*, *::before, *::after {
|
|
169
|
+
animation-duration: 0.01ms !important;
|
|
170
|
+
animation-iteration-count: 1 !important;
|
|
171
|
+
transition-duration: 0.01ms !important;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
`;
|
|
175
|
+
|
|
176
|
+
// Helper to inject theme CSS (call once at app init)
|
|
177
|
+
export function injectThemeCSS(): void {
|
|
178
|
+
if (typeof document === "undefined") return;
|
|
179
|
+
if (document.getElementById("tidecloak-theme")) return;
|
|
180
|
+
|
|
181
|
+
const style = document.createElement("style");
|
|
182
|
+
style.id = "tidecloak-theme";
|
|
183
|
+
style.textContent = themeCSS;
|
|
184
|
+
document.head.appendChild(style);
|
|
185
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tide Policy Workflow Exports
|
|
3
|
+
*
|
|
4
|
+
* These helpers provide the default Tide-based policy creation workflow.
|
|
5
|
+
* Requires heimdall-tide and asgard-tide libraries to be installed.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
loadTideLibs,
|
|
10
|
+
areTideLibsAvailable,
|
|
11
|
+
computeContractId,
|
|
12
|
+
createTidePolicyRequest,
|
|
13
|
+
createTidePolicyHandler,
|
|
14
|
+
rolePolicyToTideConfig,
|
|
15
|
+
DEFAULT_MODEL_IDS,
|
|
16
|
+
DEFAULT_ROLE_CONTRACT,
|
|
17
|
+
type TidePolicyConfig,
|
|
18
|
+
type TideContextMethods,
|
|
19
|
+
} from "./tidePolicy";
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tide Policy Workflow Helpers
|
|
3
|
+
*
|
|
4
|
+
* Provides functions for creating Tide policy requests that can be signed
|
|
5
|
+
* by the TideCloak context. Uses heimdall-tide and asgard-tide libraries.
|
|
6
|
+
*
|
|
7
|
+
* This is the default policy creation workflow used by RolesPage when
|
|
8
|
+
* Tide libraries are available.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { RolePolicy } from "../components/pages/connected/RolesPage";
|
|
12
|
+
|
|
13
|
+
// Lazy imports for optional dependencies
|
|
14
|
+
let Policy: any;
|
|
15
|
+
let PolicySignRequest: any;
|
|
16
|
+
let TideMemory: any;
|
|
17
|
+
let ApprovalType: any;
|
|
18
|
+
let ExecutionType: any;
|
|
19
|
+
|
|
20
|
+
let tideLibsLoaded = false;
|
|
21
|
+
let tideLibsAvailable = false;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Attempts to load the Tide libraries (heimdall-tide and asgard-tide).
|
|
25
|
+
* Returns true if both libraries are available, false otherwise.
|
|
26
|
+
*/
|
|
27
|
+
export async function loadTideLibs(): Promise<boolean> {
|
|
28
|
+
if (tideLibsLoaded) return tideLibsAvailable;
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const heimdall = await import("heimdall-tide");
|
|
32
|
+
const asgard = await import("asgard-tide");
|
|
33
|
+
|
|
34
|
+
Policy = heimdall.Policy;
|
|
35
|
+
PolicySignRequest = heimdall.PolicySignRequest;
|
|
36
|
+
TideMemory = heimdall.TideMemory;
|
|
37
|
+
ApprovalType = asgard.ApprovalType;
|
|
38
|
+
ExecutionType = asgard.ExecutionType;
|
|
39
|
+
|
|
40
|
+
tideLibsAvailable = true;
|
|
41
|
+
} catch {
|
|
42
|
+
tideLibsAvailable = false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
tideLibsLoaded = true;
|
|
46
|
+
return tideLibsAvailable;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Checks if Tide libraries are available without loading them.
|
|
51
|
+
* Must call loadTideLibs() first.
|
|
52
|
+
*/
|
|
53
|
+
export function areTideLibsAvailable(): boolean {
|
|
54
|
+
return tideLibsAvailable;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Default model IDs for different contract types
|
|
58
|
+
export const DEFAULT_MODEL_IDS = {
|
|
59
|
+
BASIC: "BasicCustom<Role>:BasicCustom<1>",
|
|
60
|
+
DYNAMIC: "DynamicCustom<Role>:DynamicCustom<1>",
|
|
61
|
+
DYNAMIC_APPROVED: "DynamicApprovedCustom<Role>:DynamicApprovedCustom<1>",
|
|
62
|
+
} as const;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Default Forseti contract for role-based access control.
|
|
66
|
+
* This is a simple contract that validates approvers and executors have the required role.
|
|
67
|
+
*/
|
|
68
|
+
export const DEFAULT_ROLE_CONTRACT = `using Ork.Forseti.Sdk;
|
|
69
|
+
using System;
|
|
70
|
+
using System.Collections.Generic;
|
|
71
|
+
|
|
72
|
+
/// <summary>
|
|
73
|
+
/// Default Role-Based Access Policy.
|
|
74
|
+
/// Validates that approvers and executors have the required role for a resource.
|
|
75
|
+
/// </summary>
|
|
76
|
+
public class RolePolicy : IAccessPolicy
|
|
77
|
+
{
|
|
78
|
+
[PolicyParam(Required = true, Description = "Role required for access")]
|
|
79
|
+
public string Role { get; set; }
|
|
80
|
+
|
|
81
|
+
[PolicyParam(Required = true, Description = "Resource identifier for role check")]
|
|
82
|
+
public string Resource { get; set; }
|
|
83
|
+
|
|
84
|
+
public PolicyDecision ValidateData(DataContext ctx)
|
|
85
|
+
{
|
|
86
|
+
// No data validation needed for role-based policies
|
|
87
|
+
return PolicyDecision.Allow();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public PolicyDecision ValidateApprovers(ApproversContext ctx)
|
|
91
|
+
{
|
|
92
|
+
var approvers = DokenDto.WrapAll(ctx.Dokens);
|
|
93
|
+
return Decision
|
|
94
|
+
.Require(approvers != null && approvers.Count > 0, "No approver dokens provided")
|
|
95
|
+
.RequireAnyWithRole(approvers, Resource, Role);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public PolicyDecision ValidateExecutor(ExecutorContext ctx)
|
|
99
|
+
{
|
|
100
|
+
var executor = new DokenDto(ctx.Doken);
|
|
101
|
+
return Decision
|
|
102
|
+
.RequireNotExpired(executor)
|
|
103
|
+
.RequireRole(executor, Resource, Role);
|
|
104
|
+
}
|
|
105
|
+
}`;
|
|
106
|
+
|
|
107
|
+
export interface TidePolicyConfig {
|
|
108
|
+
roleName: string;
|
|
109
|
+
threshold: number;
|
|
110
|
+
approvalType: "implicit" | "explicit";
|
|
111
|
+
executionType: "public" | "private";
|
|
112
|
+
modelId?: string;
|
|
113
|
+
resource: string;
|
|
114
|
+
vendorId: string;
|
|
115
|
+
contractCode?: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface TideContextMethods {
|
|
119
|
+
initializeTideRequest: <T extends { encode: () => Uint8Array }>(request: T) => Promise<T>;
|
|
120
|
+
getVendorId: () => string;
|
|
121
|
+
getResource: () => string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Computes the contract ID (SHA512 hash) for a Forseti contract source.
|
|
126
|
+
* This must match what the Ork server computes.
|
|
127
|
+
*/
|
|
128
|
+
export async function computeContractId(source: string): Promise<string> {
|
|
129
|
+
const encoder = new TextEncoder();
|
|
130
|
+
const data = encoder.encode(source);
|
|
131
|
+
const hashBuffer = await crypto.subtle.digest("SHA-512", data);
|
|
132
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
133
|
+
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Creates a Tide PolicySignRequest for a role policy.
|
|
138
|
+
*
|
|
139
|
+
* @param config - Policy configuration
|
|
140
|
+
* @param context - TideCloak context methods for signing
|
|
141
|
+
* @returns The signed PolicySignRequest
|
|
142
|
+
* @throws Error if Tide libraries are not available
|
|
143
|
+
*/
|
|
144
|
+
export async function createTidePolicyRequest(
|
|
145
|
+
config: TidePolicyConfig,
|
|
146
|
+
context: TideContextMethods
|
|
147
|
+
): Promise<any> {
|
|
148
|
+
if (!tideLibsAvailable) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
"Tide libraries (heimdall-tide, asgard-tide) are not available. " +
|
|
151
|
+
"Install them or provide a custom onCreatePolicy handler."
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const contractCode = config.contractCode || DEFAULT_ROLE_CONTRACT;
|
|
156
|
+
const contractId = await computeContractId(contractCode);
|
|
157
|
+
|
|
158
|
+
// Detect entry type from contract code
|
|
159
|
+
const entryType = detectEntryType(contractCode) || "RolePolicy";
|
|
160
|
+
|
|
161
|
+
// Create policy params
|
|
162
|
+
const policyParams = new Map<string, any>();
|
|
163
|
+
policyParams.set("role", config.roleName);
|
|
164
|
+
policyParams.set("threshold", config.threshold);
|
|
165
|
+
policyParams.set("resource", config.resource);
|
|
166
|
+
policyParams.set("approval_type", config.approvalType);
|
|
167
|
+
policyParams.set("execution_type", config.executionType);
|
|
168
|
+
|
|
169
|
+
// Create the Policy object
|
|
170
|
+
const policy = new Policy({
|
|
171
|
+
version: "2",
|
|
172
|
+
modelId: config.modelId || DEFAULT_MODEL_IDS.DYNAMIC_APPROVED,
|
|
173
|
+
contractId: contractId,
|
|
174
|
+
keyId: config.vendorId,
|
|
175
|
+
approvalType: config.approvalType === "explicit" ? ApprovalType.EXPLICIT : ApprovalType.IMPLICIT,
|
|
176
|
+
executionType: config.executionType === "private" ? ExecutionType.PRIVATE : ExecutionType.PUBLIC,
|
|
177
|
+
params: policyParams,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Create the PolicySignRequest
|
|
181
|
+
const policyRequest = PolicySignRequest.New(policy);
|
|
182
|
+
const policyBytes = policy.toBytes();
|
|
183
|
+
|
|
184
|
+
// Create contract transport
|
|
185
|
+
// Structure: forsetiData[1] = innerPayload = [source, entryType]
|
|
186
|
+
const contractTypeBytes = new TextEncoder().encode("forseti");
|
|
187
|
+
const sourceCodeBytes = new TextEncoder().encode(contractCode);
|
|
188
|
+
const entryTypeBytes = new TextEncoder().encode(entryType);
|
|
189
|
+
const innerPayload = TideMemory.CreateFromArray([sourceCodeBytes, entryTypeBytes]);
|
|
190
|
+
const forsetiData = TideMemory.CreateFromArray([new Uint8Array(0), innerPayload]);
|
|
191
|
+
const contractTransport = TideMemory.CreateFromArray([contractTypeBytes, forsetiData]);
|
|
192
|
+
|
|
193
|
+
const draftWithContract = TideMemory.CreateFromArray([policyBytes, contractTransport]);
|
|
194
|
+
policyRequest.draft = draftWithContract;
|
|
195
|
+
policyRequest.setCustomExpiry(604800); // 7 days
|
|
196
|
+
|
|
197
|
+
// Initialize (sign) the request using TideCloak context
|
|
198
|
+
const signedRequest = await context.initializeTideRequest(policyRequest);
|
|
199
|
+
|
|
200
|
+
return signedRequest;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Detects the entry type (class name) from C# source code.
|
|
205
|
+
* Looks for a public class implementing IAccessPolicy.
|
|
206
|
+
*/
|
|
207
|
+
function detectEntryType(source: string): string | null {
|
|
208
|
+
const match = source.match(/public\s+class\s+(\w+)\s*:\s*IAccessPolicy/);
|
|
209
|
+
return match ? match[1] : null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Creates a policy creation handler that uses the Tide workflow.
|
|
214
|
+
* This is used as the default onCreatePolicy in RolesPage when Tide libs are available.
|
|
215
|
+
*
|
|
216
|
+
* @param context - TideCloak context methods
|
|
217
|
+
* @param onRequestCreated - Optional callback when request is created (for custom handling)
|
|
218
|
+
* @returns Policy creation handler function
|
|
219
|
+
*/
|
|
220
|
+
export function createTidePolicyHandler(
|
|
221
|
+
context: TideContextMethods,
|
|
222
|
+
onRequestCreated?: (request: any, roleName: string) => Promise<void>
|
|
223
|
+
) {
|
|
224
|
+
return async (params: {
|
|
225
|
+
roleName: string;
|
|
226
|
+
policyConfig: any;
|
|
227
|
+
templateId?: string;
|
|
228
|
+
templateParams?: Record<string, any>;
|
|
229
|
+
threshold: number;
|
|
230
|
+
contractCode?: string;
|
|
231
|
+
}) => {
|
|
232
|
+
const config: TidePolicyConfig = {
|
|
233
|
+
roleName: params.roleName,
|
|
234
|
+
threshold: params.threshold,
|
|
235
|
+
approvalType: params.policyConfig?.approvalType || "explicit",
|
|
236
|
+
executionType: params.policyConfig?.executionType || "private",
|
|
237
|
+
resource: context.getResource(),
|
|
238
|
+
vendorId: context.getVendorId(),
|
|
239
|
+
contractCode: params.contractCode,
|
|
240
|
+
modelId: params.policyConfig?.modelId,
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const signedRequest = await createTidePolicyRequest(config, context);
|
|
244
|
+
|
|
245
|
+
// Call the callback if provided (e.g., to submit to backend)
|
|
246
|
+
if (onRequestCreated) {
|
|
247
|
+
await onRequestCreated(signedRequest, params.roleName);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return signedRequest;
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Converts a RolePolicy to TidePolicyConfig.
|
|
256
|
+
* Useful when loading existing policies for re-signing or updates.
|
|
257
|
+
*/
|
|
258
|
+
export function rolePolicyToTideConfig(
|
|
259
|
+
policy: RolePolicy,
|
|
260
|
+
context: TideContextMethods
|
|
261
|
+
): TidePolicyConfig {
|
|
262
|
+
return {
|
|
263
|
+
roleName: policy.roleName,
|
|
264
|
+
threshold: policy.threshold,
|
|
265
|
+
approvalType: policy.approvalType,
|
|
266
|
+
executionType: policy.executionType,
|
|
267
|
+
resource: context.getResource(),
|
|
268
|
+
vendorId: context.getVendorId(),
|
|
269
|
+
};
|
|
270
|
+
}
|