@cundi/refine-xaf 1.0.0 → 1.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 CHANGED
@@ -264,4 +264,46 @@ export const DemoObjectCreate = () => {
264
264
 
265
265
  1. **Install Dependencies**: `npm install`
266
266
  2. **Build SDK**: `npm run build`
267
- 3. **Development Mode**: `npm run dev`
267
+ 3. **Development Mode**: `npm run dev` (watch mode)
268
+ 4. **Run Tests**: `npm run test`
269
+ 5. **Test Coverage**: `npm run test:coverage`
270
+
271
+ ## Utilities
272
+
273
+ The SDK provides several utility functions:
274
+
275
+ ### Password Utilities
276
+
277
+ ```tsx
278
+ import { generatePassword, validatePasswordStrength } from "@cundi/refine-xaf";
279
+
280
+ // Generate a secure random password
281
+ const password = generatePassword(16); // Optional length, default 12
282
+
283
+ // Validate password strength
284
+ const result = validatePasswordStrength(password);
285
+ if (result.isValid) {
286
+ console.log("Password is strong!");
287
+ }
288
+ ```
289
+
290
+ ### JWT Parsing
291
+
292
+ ```tsx
293
+ import { parseJwt } from "@cundi/refine-xaf";
294
+
295
+ const token = localStorage.getItem("refine-auth");
296
+ const claims = parseJwt(token);
297
+ console.log(claims.sub, claims.exp);
298
+ ```
299
+
300
+ ## Changelog
301
+
302
+ ### v1.0.1 (Latest)
303
+ - Added `generatePassword` and `validatePasswordStrength` utilities
304
+ - Added retry mechanism to `httpClient` (exponential backoff)
305
+ - Improved `SmartList` performance with `useMemo`
306
+ - Extracted TiptapEditor styles to separate CSS file
307
+ - Enhanced environment variable validation for Keycloak
308
+ - Added Jest test framework
309
+
package/dist/index.css ADDED
@@ -0,0 +1,90 @@
1
+ /* src/styles/tiptap.css */
2
+ .ProseMirror pre {
3
+ background: var(--tiptap-code-bg, #f5f5f5);
4
+ color: var(--tiptap-code-text, inherit);
5
+ font-family:
6
+ "JetBrainsMono",
7
+ "Courier New",
8
+ monospace;
9
+ padding: 0.75rem 1rem;
10
+ border-radius: 0.5rem;
11
+ }
12
+ .ProseMirror pre code {
13
+ color: inherit;
14
+ padding: 0;
15
+ background: none;
16
+ font-size: 0.8rem;
17
+ }
18
+ .ProseMirror p {
19
+ margin-bottom: 0px;
20
+ }
21
+ .ProseMirror:focus {
22
+ outline: none;
23
+ }
24
+ .ProseMirror table {
25
+ border-collapse: collapse;
26
+ table-layout: fixed;
27
+ width: 100%;
28
+ margin: 0;
29
+ overflow: hidden;
30
+ }
31
+ .ProseMirror td,
32
+ .ProseMirror th {
33
+ min-width: 1em;
34
+ border: 2px solid var(--tiptap-border, #d9d9d9);
35
+ padding: 3px 5px;
36
+ vertical-align: top;
37
+ box-sizing: border-box;
38
+ position: relative;
39
+ }
40
+ .ProseMirror th {
41
+ font-weight: bold;
42
+ text-align: left;
43
+ background-color: var(--tiptap-header-bg, #fafafa);
44
+ }
45
+ .ProseMirror .selectedCell:after {
46
+ z-index: 2;
47
+ position: absolute;
48
+ content: "";
49
+ left: 0;
50
+ right: 0;
51
+ top: 0;
52
+ bottom: 0;
53
+ background: rgba(200, 200, 255, 0.4);
54
+ pointer-events: none;
55
+ }
56
+ .ProseMirror .column-resize-handle {
57
+ position: absolute;
58
+ right: -2px;
59
+ top: 0;
60
+ bottom: -2px;
61
+ width: 4px;
62
+ background-color: #adf;
63
+ pointer-events: none;
64
+ }
65
+ ul[data-type=taskList] {
66
+ list-style: none;
67
+ padding: 0;
68
+ }
69
+ ul[data-type=taskList] li {
70
+ display: flex;
71
+ align-items: center;
72
+ }
73
+ ul[data-type=taskList] li > label {
74
+ flex: 0 0 auto;
75
+ margin-right: 0.5rem;
76
+ user-select: none;
77
+ }
78
+ ul[data-type=taskList] li > div {
79
+ flex: 1 1 auto;
80
+ }
81
+ ul[data-type=taskList] input[type=checkbox] {
82
+ cursor: pointer;
83
+ }
84
+ .ProseMirror p.is-editor-empty:first-child::before {
85
+ color: var(--tiptap-placeholder, #bfbfbf);
86
+ content: attr(data-placeholder);
87
+ float: left;
88
+ height: 0;
89
+ pointer-events: none;
90
+ }
package/dist/index.d.mts CHANGED
@@ -6,42 +6,109 @@ declare const authProvider: AuthProvider;
6
6
 
7
7
  declare const dataProvider: (apiUrl: string) => DataProvider;
8
8
 
9
- interface IApplicationUser {
9
+ /**
10
+ * Base entity interface for XAF persistent objects
11
+ */
12
+ interface IXafEntity {
13
+ /** Object identifier (GUID) */
10
14
  Oid: string;
15
+ }
16
+ /**
17
+ * Application user entity with authentication and profile information
18
+ */
19
+ interface IApplicationUser extends IXafEntity {
20
+ /** Login username */
11
21
  UserName: string;
22
+ /** Display name shown in UI */
12
23
  DisplayName: string;
24
+ /** Email address */
13
25
  Email: string;
26
+ /** Base64 encoded profile photo */
14
27
  Photo?: string;
28
+ /** Whether the user account is active */
15
29
  IsActive: boolean;
30
+ /** Number of failed login attempts */
16
31
  AccessFailedCount: number;
32
+ /** Lockout end timestamp (ISO 8601) */
17
33
  LockoutEnd?: string;
34
+ /** Assigned roles */
18
35
  Roles?: IPermissionPolicyRole[];
19
36
  }
37
+ /**
38
+ * Permission policy for role-based access control
39
+ */
20
40
  declare enum SecurityPermissionPolicy {
41
+ /** Deny all access by default, explicitly grant permissions */
21
42
  DenyAllByDefault = "DenyAllByDefault",
43
+ /** Allow read-only access by default */
22
44
  ReadOnlyAllByDefault = "ReadOnlyAllByDefault",
45
+ /** Allow all access by default, explicitly deny permissions */
23
46
  AllowAllByDefault = "AllowAllByDefault"
24
47
  }
48
+ /**
49
+ * Permission state for type-level security
50
+ */
25
51
  declare enum SecurityPermissionState {
52
+ /** Allow the operation */
26
53
  Allow = "Allow",
54
+ /** Deny the operation */
27
55
  Deny = "Deny"
28
56
  }
29
- interface IPermissionPolicyTypePermissionObject {
30
- Oid?: string;
57
+ /**
58
+ * Type-level permission configuration for a specific business object
59
+ */
60
+ interface IPermissionPolicyTypePermissionObject extends Partial<IXafEntity> {
61
+ /** Full type name of the target business object */
31
62
  TargetType: string;
63
+ /** Read permission state */
32
64
  ReadState?: SecurityPermissionState;
65
+ /** Write/Update permission state */
33
66
  WriteState?: SecurityPermissionState;
67
+ /** Create permission state */
34
68
  CreateState?: SecurityPermissionState;
69
+ /** Delete permission state */
35
70
  DeleteState?: SecurityPermissionState;
71
+ /** Navigation permission state */
36
72
  NavigateState?: SecurityPermissionState;
37
73
  }
38
- interface IPermissionPolicyRole {
39
- Oid: string;
74
+ /**
75
+ * Permission policy role with type-level permissions
76
+ */
77
+ interface IPermissionPolicyRole extends IXafEntity {
78
+ /** Role name */
40
79
  Name: string;
80
+ /** Whether this role grants full administrative access */
41
81
  IsAdministrative: boolean;
82
+ /** Default permission policy for this role */
42
83
  PermissionPolicy: SecurityPermissionPolicy;
84
+ /** Type-level permissions for specific business objects */
43
85
  TypePermissions?: IPermissionPolicyTypePermissionObject[];
44
86
  }
87
+ /**
88
+ * JWT token claims structure
89
+ */
90
+ interface IJwtClaims {
91
+ /** Subject (user ID) */
92
+ sub?: string;
93
+ /** Token expiration timestamp */
94
+ exp?: number;
95
+ /** Token issued at timestamp */
96
+ iat?: number;
97
+ /** Issuer */
98
+ iss?: string;
99
+ /** Audience */
100
+ aud?: string;
101
+ /** Preferred username (Keycloak) */
102
+ preferred_username?: string;
103
+ /** User name claim (XAF) */
104
+ unique_name?: string;
105
+ /** Name identifier claim */
106
+ "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"?: string;
107
+ /** Name claim */
108
+ "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"?: string;
109
+ /** Allow additional claims */
110
+ [key: string]: unknown;
111
+ }
45
112
 
46
113
  declare const TOKEN_KEY = "refine-auth";
47
114
  declare class HttpError extends Error {
@@ -52,11 +119,42 @@ declare class HttpError extends Error {
52
119
  }
53
120
  interface RequestOptions extends RequestInit {
54
121
  skipAuth?: boolean;
122
+ /** Disable automatic retry for this request */
123
+ noRetry?: boolean;
55
124
  }
125
+ /**
126
+ * Get the API base URL from environment configuration
127
+ */
56
128
  declare const getBaseUrl: () => any;
57
- declare const httpClient: (endpoint: string, options?: RequestOptions) => Promise<Response | null>;
129
+ /**
130
+ * HTTP client with automatic retry for transient errors
131
+ * @param endpoint - API endpoint (relative or absolute URL)
132
+ * @param options - Request options (extends RequestInit)
133
+ * @param retryCount - Current retry attempt (internal use)
134
+ */
135
+ declare const httpClient: (endpoint: string, options?: RequestOptions, retryCount?: number) => Promise<Response | null>;
58
136
  declare const parseJwt: (token: string) => any;
59
137
 
138
+ /**
139
+ * Password generation utilities
140
+ * Provides secure random password generation using crypto API
141
+ */
142
+ /**
143
+ * Generate a random password using cryptographically secure random values
144
+ * @param length - Length of the password (default: 12)
145
+ * @returns A randomly generated password string
146
+ */
147
+ declare const generatePassword: (length?: number) => string;
148
+ /**
149
+ * Validate password strength
150
+ * @param password - Password to validate
151
+ * @returns Object with isValid flag and message
152
+ */
153
+ declare const validatePasswordStrength: (password: string) => {
154
+ isValid: boolean;
155
+ message: string;
156
+ };
157
+
60
158
  declare const authService: {
61
159
  login: ({ username, password }: any) => Promise<string>;
62
160
  getUserByUsername: (username: string) => Promise<IApplicationUser | null>;
@@ -64,6 +162,38 @@ declare const authService: {
64
162
  resetPassword: (userId: string, newPassword: string) => Promise<boolean>;
65
163
  };
66
164
 
165
+ interface KeycloakTokenResponse {
166
+ access_token: string;
167
+ id_token?: string;
168
+ expires_in: number;
169
+ refresh_expires_in: number;
170
+ refresh_token: string;
171
+ token_type: string;
172
+ "not-before-policy"?: number;
173
+ session_state?: string;
174
+ scope?: string;
175
+ }
176
+ declare const keycloakService: {
177
+ /**
178
+ * Start Authorization Code Flow with PKCE
179
+ * Redirects user to Keycloak login page
180
+ */
181
+ startAuthorizationFlow: () => Promise<void>;
182
+ /**
183
+ * Handle callback from Keycloak
184
+ * Exchange authorization code for access token
185
+ */
186
+ handleCallback: (code: string, state: string) => Promise<{
187
+ accessToken: string;
188
+ idToken: string;
189
+ }>;
190
+ /**
191
+ * Logout from Keycloak
192
+ * Redirects to Keycloak logout endpoint to properly clear the session
193
+ */
194
+ logout: (idToken?: string) => void;
195
+ };
196
+
67
197
  declare const Header: React.FC;
68
198
 
69
199
  interface SmartListProps {
@@ -107,6 +237,14 @@ declare const TiptapEditor: React.FC<TiptapEditorProps>;
107
237
 
108
238
  declare const LoginPage: React.FC;
109
239
 
240
+ declare const KeycloakLoginPage: React.FC;
241
+
242
+ /**
243
+ * Auth Callback Component
244
+ * Handles the OAuth2 callback from Keycloak after user authentication
245
+ */
246
+ declare const AuthCallback: React.FC;
247
+
110
248
  declare const ApplicationUserList: React.FC<IResourceComponentsProps>;
111
249
 
112
250
  declare const ApplicationUserCreate: React.FC<IResourceComponentsProps>;
@@ -137,4 +275,4 @@ interface IModelType {
137
275
  }
138
276
  declare const useModelTypes: () => _tanstack_react_query.UseQueryResult<IModelType[], Error>;
139
277
 
140
- export { ApplicationUserCreate, ApplicationUserEdit, ApplicationUserList, Base64Upload, ColorModeContext, ColorModeContextProvider, Header, HttpError, type IApplicationUser, type IModelType, type IPermissionPolicyRole, type IPermissionPolicyTypePermissionObject, LoginPage, RelatedList, type RequestOptions, RoleCreate, RoleEdit, RoleList, SecurityPermissionPolicy, SecurityPermissionState, SmartList, TOKEN_KEY, TiptapEditor, type TiptapEditorProps, authProvider, authService, dataProvider, getBaseUrl, httpClient, parseJwt, useColorMode, useModelTypes };
278
+ export { ApplicationUserCreate, ApplicationUserEdit, ApplicationUserList, AuthCallback, Base64Upload, ColorModeContext, ColorModeContextProvider, Header, HttpError, type IApplicationUser, type IJwtClaims, type IModelType, type IPermissionPolicyRole, type IPermissionPolicyTypePermissionObject, type IXafEntity, KeycloakLoginPage, type KeycloakTokenResponse, LoginPage, RelatedList, type RequestOptions, RoleCreate, RoleEdit, RoleList, SecurityPermissionPolicy, SecurityPermissionState, SmartList, TOKEN_KEY, TiptapEditor, type TiptapEditorProps, authProvider, authService, dataProvider, generatePassword, getBaseUrl, httpClient, keycloakService, parseJwt, useColorMode, useModelTypes, validatePasswordStrength };
package/dist/index.d.ts CHANGED
@@ -6,42 +6,109 @@ declare const authProvider: AuthProvider;
6
6
 
7
7
  declare const dataProvider: (apiUrl: string) => DataProvider;
8
8
 
9
- interface IApplicationUser {
9
+ /**
10
+ * Base entity interface for XAF persistent objects
11
+ */
12
+ interface IXafEntity {
13
+ /** Object identifier (GUID) */
10
14
  Oid: string;
15
+ }
16
+ /**
17
+ * Application user entity with authentication and profile information
18
+ */
19
+ interface IApplicationUser extends IXafEntity {
20
+ /** Login username */
11
21
  UserName: string;
22
+ /** Display name shown in UI */
12
23
  DisplayName: string;
24
+ /** Email address */
13
25
  Email: string;
26
+ /** Base64 encoded profile photo */
14
27
  Photo?: string;
28
+ /** Whether the user account is active */
15
29
  IsActive: boolean;
30
+ /** Number of failed login attempts */
16
31
  AccessFailedCount: number;
32
+ /** Lockout end timestamp (ISO 8601) */
17
33
  LockoutEnd?: string;
34
+ /** Assigned roles */
18
35
  Roles?: IPermissionPolicyRole[];
19
36
  }
37
+ /**
38
+ * Permission policy for role-based access control
39
+ */
20
40
  declare enum SecurityPermissionPolicy {
41
+ /** Deny all access by default, explicitly grant permissions */
21
42
  DenyAllByDefault = "DenyAllByDefault",
43
+ /** Allow read-only access by default */
22
44
  ReadOnlyAllByDefault = "ReadOnlyAllByDefault",
45
+ /** Allow all access by default, explicitly deny permissions */
23
46
  AllowAllByDefault = "AllowAllByDefault"
24
47
  }
48
+ /**
49
+ * Permission state for type-level security
50
+ */
25
51
  declare enum SecurityPermissionState {
52
+ /** Allow the operation */
26
53
  Allow = "Allow",
54
+ /** Deny the operation */
27
55
  Deny = "Deny"
28
56
  }
29
- interface IPermissionPolicyTypePermissionObject {
30
- Oid?: string;
57
+ /**
58
+ * Type-level permission configuration for a specific business object
59
+ */
60
+ interface IPermissionPolicyTypePermissionObject extends Partial<IXafEntity> {
61
+ /** Full type name of the target business object */
31
62
  TargetType: string;
63
+ /** Read permission state */
32
64
  ReadState?: SecurityPermissionState;
65
+ /** Write/Update permission state */
33
66
  WriteState?: SecurityPermissionState;
67
+ /** Create permission state */
34
68
  CreateState?: SecurityPermissionState;
69
+ /** Delete permission state */
35
70
  DeleteState?: SecurityPermissionState;
71
+ /** Navigation permission state */
36
72
  NavigateState?: SecurityPermissionState;
37
73
  }
38
- interface IPermissionPolicyRole {
39
- Oid: string;
74
+ /**
75
+ * Permission policy role with type-level permissions
76
+ */
77
+ interface IPermissionPolicyRole extends IXafEntity {
78
+ /** Role name */
40
79
  Name: string;
80
+ /** Whether this role grants full administrative access */
41
81
  IsAdministrative: boolean;
82
+ /** Default permission policy for this role */
42
83
  PermissionPolicy: SecurityPermissionPolicy;
84
+ /** Type-level permissions for specific business objects */
43
85
  TypePermissions?: IPermissionPolicyTypePermissionObject[];
44
86
  }
87
+ /**
88
+ * JWT token claims structure
89
+ */
90
+ interface IJwtClaims {
91
+ /** Subject (user ID) */
92
+ sub?: string;
93
+ /** Token expiration timestamp */
94
+ exp?: number;
95
+ /** Token issued at timestamp */
96
+ iat?: number;
97
+ /** Issuer */
98
+ iss?: string;
99
+ /** Audience */
100
+ aud?: string;
101
+ /** Preferred username (Keycloak) */
102
+ preferred_username?: string;
103
+ /** User name claim (XAF) */
104
+ unique_name?: string;
105
+ /** Name identifier claim */
106
+ "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"?: string;
107
+ /** Name claim */
108
+ "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"?: string;
109
+ /** Allow additional claims */
110
+ [key: string]: unknown;
111
+ }
45
112
 
46
113
  declare const TOKEN_KEY = "refine-auth";
47
114
  declare class HttpError extends Error {
@@ -52,11 +119,42 @@ declare class HttpError extends Error {
52
119
  }
53
120
  interface RequestOptions extends RequestInit {
54
121
  skipAuth?: boolean;
122
+ /** Disable automatic retry for this request */
123
+ noRetry?: boolean;
55
124
  }
125
+ /**
126
+ * Get the API base URL from environment configuration
127
+ */
56
128
  declare const getBaseUrl: () => any;
57
- declare const httpClient: (endpoint: string, options?: RequestOptions) => Promise<Response | null>;
129
+ /**
130
+ * HTTP client with automatic retry for transient errors
131
+ * @param endpoint - API endpoint (relative or absolute URL)
132
+ * @param options - Request options (extends RequestInit)
133
+ * @param retryCount - Current retry attempt (internal use)
134
+ */
135
+ declare const httpClient: (endpoint: string, options?: RequestOptions, retryCount?: number) => Promise<Response | null>;
58
136
  declare const parseJwt: (token: string) => any;
59
137
 
138
+ /**
139
+ * Password generation utilities
140
+ * Provides secure random password generation using crypto API
141
+ */
142
+ /**
143
+ * Generate a random password using cryptographically secure random values
144
+ * @param length - Length of the password (default: 12)
145
+ * @returns A randomly generated password string
146
+ */
147
+ declare const generatePassword: (length?: number) => string;
148
+ /**
149
+ * Validate password strength
150
+ * @param password - Password to validate
151
+ * @returns Object with isValid flag and message
152
+ */
153
+ declare const validatePasswordStrength: (password: string) => {
154
+ isValid: boolean;
155
+ message: string;
156
+ };
157
+
60
158
  declare const authService: {
61
159
  login: ({ username, password }: any) => Promise<string>;
62
160
  getUserByUsername: (username: string) => Promise<IApplicationUser | null>;
@@ -64,6 +162,38 @@ declare const authService: {
64
162
  resetPassword: (userId: string, newPassword: string) => Promise<boolean>;
65
163
  };
66
164
 
165
+ interface KeycloakTokenResponse {
166
+ access_token: string;
167
+ id_token?: string;
168
+ expires_in: number;
169
+ refresh_expires_in: number;
170
+ refresh_token: string;
171
+ token_type: string;
172
+ "not-before-policy"?: number;
173
+ session_state?: string;
174
+ scope?: string;
175
+ }
176
+ declare const keycloakService: {
177
+ /**
178
+ * Start Authorization Code Flow with PKCE
179
+ * Redirects user to Keycloak login page
180
+ */
181
+ startAuthorizationFlow: () => Promise<void>;
182
+ /**
183
+ * Handle callback from Keycloak
184
+ * Exchange authorization code for access token
185
+ */
186
+ handleCallback: (code: string, state: string) => Promise<{
187
+ accessToken: string;
188
+ idToken: string;
189
+ }>;
190
+ /**
191
+ * Logout from Keycloak
192
+ * Redirects to Keycloak logout endpoint to properly clear the session
193
+ */
194
+ logout: (idToken?: string) => void;
195
+ };
196
+
67
197
  declare const Header: React.FC;
68
198
 
69
199
  interface SmartListProps {
@@ -107,6 +237,14 @@ declare const TiptapEditor: React.FC<TiptapEditorProps>;
107
237
 
108
238
  declare const LoginPage: React.FC;
109
239
 
240
+ declare const KeycloakLoginPage: React.FC;
241
+
242
+ /**
243
+ * Auth Callback Component
244
+ * Handles the OAuth2 callback from Keycloak after user authentication
245
+ */
246
+ declare const AuthCallback: React.FC;
247
+
110
248
  declare const ApplicationUserList: React.FC<IResourceComponentsProps>;
111
249
 
112
250
  declare const ApplicationUserCreate: React.FC<IResourceComponentsProps>;
@@ -137,4 +275,4 @@ interface IModelType {
137
275
  }
138
276
  declare const useModelTypes: () => _tanstack_react_query.UseQueryResult<IModelType[], Error>;
139
277
 
140
- export { ApplicationUserCreate, ApplicationUserEdit, ApplicationUserList, Base64Upload, ColorModeContext, ColorModeContextProvider, Header, HttpError, type IApplicationUser, type IModelType, type IPermissionPolicyRole, type IPermissionPolicyTypePermissionObject, LoginPage, RelatedList, type RequestOptions, RoleCreate, RoleEdit, RoleList, SecurityPermissionPolicy, SecurityPermissionState, SmartList, TOKEN_KEY, TiptapEditor, type TiptapEditorProps, authProvider, authService, dataProvider, getBaseUrl, httpClient, parseJwt, useColorMode, useModelTypes };
278
+ export { ApplicationUserCreate, ApplicationUserEdit, ApplicationUserList, AuthCallback, Base64Upload, ColorModeContext, ColorModeContextProvider, Header, HttpError, type IApplicationUser, type IJwtClaims, type IModelType, type IPermissionPolicyRole, type IPermissionPolicyTypePermissionObject, type IXafEntity, KeycloakLoginPage, type KeycloakTokenResponse, LoginPage, RelatedList, type RequestOptions, RoleCreate, RoleEdit, RoleList, SecurityPermissionPolicy, SecurityPermissionState, SmartList, TOKEN_KEY, TiptapEditor, type TiptapEditorProps, authProvider, authService, dataProvider, generatePassword, getBaseUrl, httpClient, keycloakService, parseJwt, useColorMode, useModelTypes, validatePasswordStrength };