@stackwright-pro/auth 0.2.0-alpha.13 → 0.2.0-alpha.15
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/dist/client.d.mts +197 -0
- package/dist/client.d.ts +197 -0
- package/dist/client.js +345 -0
- package/dist/client.js.map +1 -0
- package/dist/client.mjs +334 -0
- package/dist/client.mjs.map +1 -0
- package/dist/index.d.mts +6 -200
- package/dist/index.d.ts +6 -200
- package/dist/index.js +0 -194
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -185
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -2
package/dist/client.js
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
|
|
7
|
+
var AuthContext = react.createContext(null);
|
|
8
|
+
function useAuth() {
|
|
9
|
+
const context = react.useContext(AuthContext);
|
|
10
|
+
if (!context) {
|
|
11
|
+
throw new Error("useAuth must be used within AuthProvider");
|
|
12
|
+
}
|
|
13
|
+
return context;
|
|
14
|
+
}
|
|
15
|
+
function useRequireAuth() {
|
|
16
|
+
const auth = useAuth();
|
|
17
|
+
if (!auth.isAuthenticated) {
|
|
18
|
+
console.warn("useRequireAuth: User is not authenticated");
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return auth;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/rbac/rbac-engine.ts
|
|
25
|
+
var RBACEngine = class {
|
|
26
|
+
config;
|
|
27
|
+
rolePermissions;
|
|
28
|
+
constructor(config) {
|
|
29
|
+
this.config = config;
|
|
30
|
+
this.rolePermissions = this.buildRolePermissionMap();
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Build internal map of role → permissions for fast lookups
|
|
34
|
+
*/
|
|
35
|
+
buildRolePermissionMap() {
|
|
36
|
+
const map = /* @__PURE__ */ new Map();
|
|
37
|
+
for (const role of this.config.roles) {
|
|
38
|
+
map.set(role.name, new Set(role.permissions || []));
|
|
39
|
+
}
|
|
40
|
+
return map;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Check if user has a specific role
|
|
44
|
+
*/
|
|
45
|
+
hasRole(user, role) {
|
|
46
|
+
return user.roles.includes(role);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Check if user has any of the specified roles
|
|
50
|
+
*/
|
|
51
|
+
hasAnyRole(user, roles) {
|
|
52
|
+
return roles.some((role) => this.hasRole(user, role));
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Check if user has all of the specified roles
|
|
56
|
+
*/
|
|
57
|
+
hasAllRoles(user, roles) {
|
|
58
|
+
return roles.every((role) => this.hasRole(user, role));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Check if user has a specific permission
|
|
62
|
+
* Permissions are checked both:
|
|
63
|
+
* 1. Directly in user.permissions array
|
|
64
|
+
* 2. Through role-based permissions from config
|
|
65
|
+
*/
|
|
66
|
+
hasPermission(user, permission) {
|
|
67
|
+
if (user.permissions?.includes(permission)) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
for (const role of user.roles) {
|
|
71
|
+
const permissions = this.rolePermissions.get(role);
|
|
72
|
+
if (permissions?.has(permission)) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
if (permissions) {
|
|
76
|
+
for (const p of permissions) {
|
|
77
|
+
if (p.endsWith(":*")) {
|
|
78
|
+
const prefix = p.slice(0, -1);
|
|
79
|
+
if (permission.startsWith(prefix)) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Check if user has any of the specified permissions
|
|
90
|
+
*/
|
|
91
|
+
hasAnyPermission(user, permissions) {
|
|
92
|
+
return permissions.some((permission) => this.hasPermission(user, permission));
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Check if user has all of the specified permissions
|
|
96
|
+
*/
|
|
97
|
+
hasAllPermissions(user, permissions) {
|
|
98
|
+
return permissions.every((permission) => this.hasPermission(user, permission));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Check if route is public (no auth required)
|
|
102
|
+
*/
|
|
103
|
+
isPublicRoute(path) {
|
|
104
|
+
if (!this.config.public_routes) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
return this.config.public_routes.some((publicPath) => {
|
|
108
|
+
return this.matchPath(path, publicPath);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Check if user can access a route based on protected_routes config
|
|
113
|
+
*/
|
|
114
|
+
canAccessRoute(user, path) {
|
|
115
|
+
if (this.isPublicRoute(path)) {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
if (!user) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
if (!this.config.protected_routes || this.config.protected_routes.length === 0) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
const matchingRoute = this.config.protected_routes.find((route) => {
|
|
125
|
+
return this.matchPath(path, route.path);
|
|
126
|
+
});
|
|
127
|
+
if (!matchingRoute) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
return this.hasAnyRole(user, matchingRoute.roles);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Check if user can access a component based on component auth config
|
|
134
|
+
*/
|
|
135
|
+
canAccessComponent(user, authConfig) {
|
|
136
|
+
if (!authConfig) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
if (!user) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
if (authConfig.required_roles && authConfig.required_roles.length > 0) {
|
|
143
|
+
if (!this.hasAnyRole(user, authConfig.required_roles)) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (authConfig.required_permissions && authConfig.required_permissions.length > 0) {
|
|
148
|
+
if (!this.hasAllPermissions(user, authConfig.required_permissions)) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
// Match a path against a pattern with wildcard support
|
|
155
|
+
matchPath(path, pattern) {
|
|
156
|
+
if (path === pattern) {
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
if (!pattern.includes("*")) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
163
|
+
const regex = new RegExp(`^${escaped}$`);
|
|
164
|
+
return regex.test(path);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
function AuthProvider({
|
|
168
|
+
user,
|
|
169
|
+
session,
|
|
170
|
+
rbacConfig,
|
|
171
|
+
isLoading = false,
|
|
172
|
+
children
|
|
173
|
+
}) {
|
|
174
|
+
const rbac = react.useMemo(() => new RBACEngine(rbacConfig), [rbacConfig]);
|
|
175
|
+
const value = react.useMemo(
|
|
176
|
+
() => ({
|
|
177
|
+
user,
|
|
178
|
+
session,
|
|
179
|
+
isAuthenticated: user !== null,
|
|
180
|
+
isLoading,
|
|
181
|
+
hasRole: (role) => {
|
|
182
|
+
if (!user) return false;
|
|
183
|
+
return rbac.hasRole(user, role);
|
|
184
|
+
},
|
|
185
|
+
hasPermission: (permission) => {
|
|
186
|
+
if (!user) return false;
|
|
187
|
+
return rbac.hasPermission(user, permission);
|
|
188
|
+
},
|
|
189
|
+
hasAnyRole: (roles) => {
|
|
190
|
+
if (!user) return false;
|
|
191
|
+
return rbac.hasAnyRole(user, roles);
|
|
192
|
+
},
|
|
193
|
+
hasAllPermissions: (permissions) => {
|
|
194
|
+
if (!user) return false;
|
|
195
|
+
return rbac.hasAllPermissions(user, permissions);
|
|
196
|
+
}
|
|
197
|
+
}),
|
|
198
|
+
[user, session, isLoading, rbac]
|
|
199
|
+
);
|
|
200
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AuthContext.Provider, { value, children });
|
|
201
|
+
}
|
|
202
|
+
var FallbackComponents = {
|
|
203
|
+
/**
|
|
204
|
+
* Hide component (render nothing)
|
|
205
|
+
*/
|
|
206
|
+
hide: () => null,
|
|
207
|
+
/**
|
|
208
|
+
* Show placeholder message
|
|
209
|
+
*/
|
|
210
|
+
placeholder: ({ className }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
211
|
+
"div",
|
|
212
|
+
{
|
|
213
|
+
className: className || "auth-placeholder",
|
|
214
|
+
style: {
|
|
215
|
+
padding: "1rem",
|
|
216
|
+
border: "1px dashed #ccc",
|
|
217
|
+
borderRadius: "4px",
|
|
218
|
+
color: "#666",
|
|
219
|
+
fontStyle: "italic",
|
|
220
|
+
textAlign: "center"
|
|
221
|
+
},
|
|
222
|
+
children: "Content requires authorization"
|
|
223
|
+
}
|
|
224
|
+
),
|
|
225
|
+
/**
|
|
226
|
+
* Show custom message
|
|
227
|
+
*/
|
|
228
|
+
message: ({ message, className }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
229
|
+
"div",
|
|
230
|
+
{
|
|
231
|
+
className: className || "auth-message",
|
|
232
|
+
style: {
|
|
233
|
+
padding: "1rem",
|
|
234
|
+
border: "1px solid #f0ad4e",
|
|
235
|
+
borderRadius: "4px",
|
|
236
|
+
backgroundColor: "#fcf8e3",
|
|
237
|
+
color: "#8a6d3b"
|
|
238
|
+
},
|
|
239
|
+
children: message || "Unauthorized"
|
|
240
|
+
}
|
|
241
|
+
)
|
|
242
|
+
};
|
|
243
|
+
function withAuth(Component, authConfig) {
|
|
244
|
+
if (!authConfig) {
|
|
245
|
+
return Component;
|
|
246
|
+
}
|
|
247
|
+
const WrappedComponent = (props) => {
|
|
248
|
+
const auth = useAuth();
|
|
249
|
+
if (authConfig.required_roles && authConfig.required_roles.length > 0) {
|
|
250
|
+
if (!auth.hasAnyRole(authConfig.required_roles)) {
|
|
251
|
+
return renderFallback(authConfig);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (authConfig.required_permissions && authConfig.required_permissions.length > 0) {
|
|
255
|
+
if (!auth.hasAllPermissions(authConfig.required_permissions)) {
|
|
256
|
+
return renderFallback(authConfig);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Component, { ...props });
|
|
260
|
+
};
|
|
261
|
+
const componentName = Component.displayName || Component.name || "Component";
|
|
262
|
+
WrappedComponent.displayName = `withAuth(${componentName})`;
|
|
263
|
+
return WrappedComponent;
|
|
264
|
+
}
|
|
265
|
+
function renderFallback(authConfig) {
|
|
266
|
+
const fallbackType = authConfig.fallback || "hide";
|
|
267
|
+
switch (fallbackType) {
|
|
268
|
+
case "hide":
|
|
269
|
+
return FallbackComponents.hide();
|
|
270
|
+
case "placeholder":
|
|
271
|
+
return FallbackComponents.placeholder({});
|
|
272
|
+
case "message":
|
|
273
|
+
return FallbackComponents.message({
|
|
274
|
+
message: authConfig.fallback_message
|
|
275
|
+
});
|
|
276
|
+
default:
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function withAuthFallback(Component, authConfig, FallbackComponent) {
|
|
281
|
+
const WrappedComponent = (props) => {
|
|
282
|
+
const auth = useAuth();
|
|
283
|
+
const isAuthorized = checkAuthorization(auth, authConfig);
|
|
284
|
+
if (!isAuthorized) {
|
|
285
|
+
return /* @__PURE__ */ jsxRuntime.jsx(FallbackComponent, {});
|
|
286
|
+
}
|
|
287
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Component, { ...props });
|
|
288
|
+
};
|
|
289
|
+
const componentName = Component.displayName || Component.name || "Component";
|
|
290
|
+
WrappedComponent.displayName = `withAuthFallback(${componentName})`;
|
|
291
|
+
return WrappedComponent;
|
|
292
|
+
}
|
|
293
|
+
function checkAuthorization(auth, authConfig) {
|
|
294
|
+
if (authConfig.required_roles && authConfig.required_roles.length > 0) {
|
|
295
|
+
if (!auth.hasAnyRole(authConfig.required_roles)) {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (authConfig.required_permissions && authConfig.required_permissions.length > 0) {
|
|
300
|
+
if (!auth.hasAllPermissions(authConfig.required_permissions)) {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// src/registration.ts
|
|
308
|
+
var authDecoratorRegistry = {
|
|
309
|
+
decorator: null
|
|
310
|
+
};
|
|
311
|
+
function registerAuthDecorator() {
|
|
312
|
+
authDecoratorRegistry.decorator = withAuth;
|
|
313
|
+
if (typeof window !== "undefined" && window.__STACKWRIGHT_DEBUG__) {
|
|
314
|
+
console.log("\u{1F510} Auth decorator registered");
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function getAuthDecorator() {
|
|
318
|
+
return authDecoratorRegistry.decorator;
|
|
319
|
+
}
|
|
320
|
+
function maybeWrapWithAuth(Component, authConfig) {
|
|
321
|
+
const decorator = getAuthDecorator();
|
|
322
|
+
if (!decorator || !authConfig) {
|
|
323
|
+
return Component;
|
|
324
|
+
}
|
|
325
|
+
return decorator(Component, authConfig);
|
|
326
|
+
}
|
|
327
|
+
function hasAuthConfig(item) {
|
|
328
|
+
if (!item || typeof item !== "object") {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
return "auth" in item;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
exports.AuthContext = AuthContext;
|
|
335
|
+
exports.AuthProvider = AuthProvider;
|
|
336
|
+
exports.getAuthDecorator = getAuthDecorator;
|
|
337
|
+
exports.hasAuthConfig = hasAuthConfig;
|
|
338
|
+
exports.maybeWrapWithAuth = maybeWrapWithAuth;
|
|
339
|
+
exports.registerAuthDecorator = registerAuthDecorator;
|
|
340
|
+
exports.useAuth = useAuth;
|
|
341
|
+
exports.useRequireAuth = useRequireAuth;
|
|
342
|
+
exports.withAuth = withAuth;
|
|
343
|
+
exports.withAuthFallback = withAuthFallback;
|
|
344
|
+
//# sourceMappingURL=client.js.map
|
|
345
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/context/AuthContext.tsx","../src/rbac/rbac-engine.ts","../src/context/AuthProvider.tsx","../src/decorators/withAuth.tsx","../src/registration.ts"],"names":["createContext","useContext","useMemo","jsx"],"mappings":"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","file":"client.js","sourcesContent":["import { createContext, useContext } from 'react';\nimport type { AuthUser, AuthSession } from '../types/index.js';\n\n/**\n * Auth context value provided to component tree\n */\nexport interface AuthContextValue {\n /**\n * Currently authenticated user (null if not authenticated)\n */\n user: AuthUser | null;\n\n /**\n * Current session (null if not authenticated)\n */\n session: AuthSession | null;\n\n /**\n * Whether user is authenticated\n */\n isAuthenticated: boolean;\n\n /**\n * Whether auth is still loading\n */\n isLoading: boolean;\n\n /**\n * Check if user has a specific role\n */\n hasRole: (role: string) => boolean;\n\n /**\n * Check if user has a specific permission\n */\n hasPermission: (permission: string) => boolean;\n\n /**\n * Check if user has any of the specified roles\n */\n hasAnyRole: (roles: string[]) => boolean;\n\n /**\n * Check if user has all of the specified permissions\n */\n hasAllPermissions: (permissions: string[]) => boolean;\n}\n\n/**\n * Auth context - provides authentication state to components\n */\nexport const AuthContext = createContext<AuthContextValue | null>(null);\n\n/**\n * Hook to access auth context\n * Throws error if used outside AuthProvider\n */\nexport function useAuth(): AuthContextValue {\n const context = useContext(AuthContext);\n\n if (!context) {\n throw new Error('useAuth must be used within AuthProvider');\n }\n\n return context;\n}\n\n/**\n * Hook to require authentication\n * Returns null and logs warning if not authenticated\n */\nexport function useRequireAuth(): AuthContextValue | null {\n const auth = useAuth();\n\n if (!auth.isAuthenticated) {\n console.warn('useRequireAuth: User is not authenticated');\n return null;\n }\n\n return auth;\n}\n","import type { AuthUser, RBACConfig, ComponentAuthConfig } from '../types/index.js';\n\n/**\n * RBAC Engine for checking roles and permissions\n */\nexport class RBACEngine {\n private config: RBACConfig;\n private rolePermissions: Map<string, Set<string>>;\n\n constructor(config: RBACConfig) {\n this.config = config;\n this.rolePermissions = this.buildRolePermissionMap();\n }\n\n /**\n * Build internal map of role → permissions for fast lookups\n */\n private buildRolePermissionMap(): Map<string, Set<string>> {\n const map = new Map<string, Set<string>>();\n\n for (const role of this.config.roles) {\n map.set(role.name, new Set(role.permissions || []));\n }\n\n return map;\n }\n\n /**\n * Check if user has a specific role\n */\n hasRole(user: AuthUser, role: string): boolean {\n return user.roles.includes(role);\n }\n\n /**\n * Check if user has any of the specified roles\n */\n hasAnyRole(user: AuthUser, roles: string[]): boolean {\n return roles.some((role) => this.hasRole(user, role));\n }\n\n /**\n * Check if user has all of the specified roles\n */\n hasAllRoles(user: AuthUser, roles: string[]): boolean {\n return roles.every((role) => this.hasRole(user, role));\n }\n\n /**\n * Check if user has a specific permission\n * Permissions are checked both:\n * 1. Directly in user.permissions array\n * 2. Through role-based permissions from config\n */\n hasPermission(user: AuthUser, permission: string): boolean {\n // Check direct permissions\n if (user.permissions?.includes(permission)) {\n return true;\n }\n\n // Check role-based permissions\n for (const role of user.roles) {\n const permissions = this.rolePermissions.get(role);\n if (permissions?.has(permission)) {\n return true;\n }\n\n // Check wildcard permissions (e.g., admin:* grants admin:read, admin:write)\n if (permissions) {\n for (const p of permissions) {\n if (p.endsWith(':*')) {\n const prefix = p.slice(0, -1); // Remove the *\n if (permission.startsWith(prefix)) {\n return true;\n }\n }\n }\n }\n }\n\n return false;\n }\n\n /**\n * Check if user has any of the specified permissions\n */\n hasAnyPermission(user: AuthUser, permissions: string[]): boolean {\n return permissions.some((permission) => this.hasPermission(user, permission));\n }\n\n /**\n * Check if user has all of the specified permissions\n */\n hasAllPermissions(user: AuthUser, permissions: string[]): boolean {\n return permissions.every((permission) => this.hasPermission(user, permission));\n }\n\n /**\n * Check if route is public (no auth required)\n */\n isPublicRoute(path: string): boolean {\n if (!this.config.public_routes) {\n return false;\n }\n\n return this.config.public_routes.some((publicPath) => {\n return this.matchPath(path, publicPath);\n });\n }\n\n /**\n * Check if user can access a route based on protected_routes config\n */\n canAccessRoute(user: AuthUser | null, path: string): boolean {\n // Check if route is public\n if (this.isPublicRoute(path)) {\n return true;\n }\n\n // No user = can't access protected routes\n if (!user) {\n return false;\n }\n\n // If no protected routes configured, allow by default\n if (!this.config.protected_routes || this.config.protected_routes.length === 0) {\n return true;\n }\n\n // Find matching protected route\n const matchingRoute = this.config.protected_routes.find((route) => {\n return this.matchPath(path, route.path);\n });\n\n // If no matching protected route, allow (route not explicitly protected)\n if (!matchingRoute) {\n return true;\n }\n\n // Check if user has required role for this route\n return this.hasAnyRole(user, matchingRoute.roles);\n }\n\n /**\n * Check if user can access a component based on component auth config\n */\n canAccessComponent(user: AuthUser | null, authConfig: ComponentAuthConfig | undefined): boolean {\n // No auth config = public component\n if (!authConfig) {\n return true;\n }\n\n // No user = can't access protected component\n if (!user) {\n return false;\n }\n\n // Check required roles\n if (authConfig.required_roles && authConfig.required_roles.length > 0) {\n if (!this.hasAnyRole(user, authConfig.required_roles)) {\n return false;\n }\n }\n\n // Check required permissions\n if (authConfig.required_permissions && authConfig.required_permissions.length > 0) {\n if (!this.hasAllPermissions(user, authConfig.required_permissions)) {\n return false;\n }\n }\n\n return true;\n }\n\n // Match a path against a pattern with wildcard support\n private matchPath(path: string, pattern: string): boolean {\n // Exact match\n if (path === pattern) {\n return true;\n }\n\n // No wildcards in pattern — only exact match applies\n if (!pattern.includes('*')) {\n return false;\n }\n\n // Escape ALL regex metacharacters, then convert glob * to .*\n // This prevents ReDoS from crafted route patterns in YAML config\n const escaped = pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&') // Escape regex specials\n .replace(/\\*/g, '.*'); // Convert glob * to .*\n\n const regex = new RegExp(`^${escaped}$`);\n return regex.test(path);\n }\n}\n","import React, { useMemo, type ReactNode, type ReactElement } from 'react';\nimport { AuthContext, type AuthContextValue } from './AuthContext.js';\nimport { RBACEngine } from '../rbac/rbac-engine.js';\nimport type { AuthUser, AuthSession, RBACConfig } from '../types/index.js';\n\nexport interface AuthProviderProps {\n /**\n * Current authenticated user (null if not authenticated)\n */\n user: AuthUser | null;\n\n /**\n * Current session (null if not authenticated)\n */\n session: AuthSession | null;\n\n /**\n * RBAC configuration for role/permission checking\n */\n rbacConfig: RBACConfig;\n\n /**\n * Whether auth is still loading\n */\n isLoading?: boolean;\n\n /**\n * Child components\n */\n children: ReactNode;\n}\n\n/**\n * AuthProvider - Provides authentication state to component tree\n *\n * @example\n * ```tsx\n * <AuthProvider user={user} session={session} rbacConfig={config}>\n * <App />\n * </AuthProvider>\n * ```\n */\nexport function AuthProvider({\n user,\n session,\n rbacConfig,\n isLoading = false,\n children,\n}: AuthProviderProps): ReactElement {\n // Create RBAC engine for role/permission checking\n const rbac = useMemo(() => new RBACEngine(rbacConfig), [rbacConfig]);\n\n // Build context value with helper functions\n const value: AuthContextValue = useMemo(\n () => ({\n user,\n session,\n isAuthenticated: user !== null,\n isLoading,\n\n hasRole: (role: string) => {\n if (!user) return false;\n return rbac.hasRole(user, role);\n },\n\n hasPermission: (permission: string) => {\n if (!user) return false;\n return rbac.hasPermission(user, permission);\n },\n\n hasAnyRole: (roles: string[]) => {\n if (!user) return false;\n return rbac.hasAnyRole(user, roles);\n },\n\n hasAllPermissions: (permissions: string[]) => {\n if (!user) return false;\n return rbac.hasAllPermissions(user, permissions);\n },\n }),\n [user, session, isLoading, rbac]\n );\n\n return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;\n}\n","import React from 'react';\nimport { useAuth } from '../context/AuthContext.js';\nimport type { ComponentAuthConfig } from '../types/index.js';\n\n/**\n * Props that any component wrapped with withAuth will receive\n */\nexport interface ComponentProps {\n id?: string;\n [key: string]: any;\n}\n\n/**\n * Fallback component props\n */\ninterface FallbackProps {\n message?: string;\n className?: string;\n}\n\n/**\n * Default fallback components for unauthorized access\n */\nconst FallbackComponents = {\n /**\n * Hide component (render nothing)\n */\n hide: () => null,\n\n /**\n * Show placeholder message\n */\n placeholder: ({ className }: FallbackProps) => (\n <div\n className={className || 'auth-placeholder'}\n style={{\n padding: '1rem',\n border: '1px dashed #ccc',\n borderRadius: '4px',\n color: '#666',\n fontStyle: 'italic',\n textAlign: 'center',\n }}\n >\n Content requires authorization\n </div>\n ),\n\n /**\n * Show custom message\n */\n message: ({ message, className }: FallbackProps) => (\n <div\n className={className || 'auth-message'}\n style={{\n padding: '1rem',\n border: '1px solid #f0ad4e',\n borderRadius: '4px',\n backgroundColor: '#fcf8e3',\n color: '#8a6d3b',\n }}\n >\n {message || 'Unauthorized'}\n </div>\n ),\n};\n\n/**\n * Higher-order component that wraps a component with authentication checks\n *\n * @example\n * ```tsx\n * const ProtectedButton = withAuth(Button, {\n * required_roles: ['ADMIN'],\n * fallback: 'message',\n * fallback_message: 'Only admins can see this button'\n * });\n * ```\n *\n * @param Component - The component to wrap\n * @param authConfig - Authentication requirements from YAML\n * @returns Wrapped component with auth enforcement\n */\nexport function withAuth<P extends ComponentProps>(\n Component: React.ComponentType<P>,\n authConfig?: ComponentAuthConfig\n): React.ComponentType<P> {\n // If no auth config, return component unchanged\n if (!authConfig) {\n return Component;\n }\n\n // Create wrapped component with display name for debugging\n const WrappedComponent = (props: P) => {\n const auth = useAuth();\n\n // Check role requirements\n if (authConfig.required_roles && authConfig.required_roles.length > 0) {\n if (!auth.hasAnyRole(authConfig.required_roles)) {\n return renderFallback(authConfig);\n }\n }\n\n // Check permission requirements (must have ALL permissions)\n if (authConfig.required_permissions && authConfig.required_permissions.length > 0) {\n if (!auth.hasAllPermissions(authConfig.required_permissions)) {\n return renderFallback(authConfig);\n }\n }\n\n // User authorized - render component\n return <Component {...props} />;\n };\n\n // Set display name for React DevTools\n const componentName = Component.displayName || Component.name || 'Component';\n WrappedComponent.displayName = `withAuth(${componentName})`;\n\n return WrappedComponent;\n}\n\n/**\n * Render fallback component based on configuration\n */\nfunction renderFallback(authConfig: ComponentAuthConfig): React.ReactElement | null {\n const fallbackType = authConfig.fallback || 'hide';\n\n switch (fallbackType) {\n case 'hide':\n return FallbackComponents.hide();\n\n case 'placeholder':\n return FallbackComponents.placeholder({});\n\n case 'message':\n return FallbackComponents.message({\n message: authConfig.fallback_message,\n });\n\n default:\n return null;\n }\n}\n\n/**\n * Custom fallback component (for advanced use cases)\n */\nexport function withAuthFallback<P extends ComponentProps>(\n Component: React.ComponentType<P>,\n authConfig: ComponentAuthConfig,\n FallbackComponent: React.ComponentType<any>\n): React.ComponentType<P> {\n const WrappedComponent = (props: P) => {\n const auth = useAuth();\n\n // Check authorization\n const isAuthorized = checkAuthorization(auth, authConfig);\n\n if (!isAuthorized) {\n return <FallbackComponent />;\n }\n\n return <Component {...props} />;\n };\n\n const componentName = Component.displayName || Component.name || 'Component';\n WrappedComponent.displayName = `withAuthFallback(${componentName})`;\n\n return WrappedComponent;\n}\n\n/**\n * Check if user is authorized based on auth config\n */\nfunction checkAuthorization(\n auth: ReturnType<typeof useAuth>,\n authConfig: ComponentAuthConfig\n): boolean {\n // Check roles\n if (authConfig.required_roles && authConfig.required_roles.length > 0) {\n if (!auth.hasAnyRole(authConfig.required_roles)) {\n return false;\n }\n }\n\n // Check permissions\n if (authConfig.required_permissions && authConfig.required_permissions.length > 0) {\n if (!auth.hasAllPermissions(authConfig.required_permissions)) {\n return false;\n }\n }\n\n return true;\n}\n","import { withAuth } from './decorators/withAuth.js';\nimport type { ComponentAuthConfig } from './types/index.js';\nimport type React from 'react';\n\n/**\n * Global registry for auth decorator function\n * This allows OSS packages to optionally use auth without depending on it\n *\n * Design principle: No hard dependencies!\n * - OSS core never imports from pro packages\n * - Pro package registers itself at runtime\n * - User app is the glue that connects them\n */\nconst authDecoratorRegistry: {\n decorator: typeof withAuth | null;\n} = {\n decorator: null,\n};\n\n/**\n * Register the auth decorator for use by content renderers\n *\n * This should be called once in your app's initialization (e.g., _app.tsx)\n * to enable auth-aware component rendering.\n *\n * @example\n * ```tsx\n * // In pages/_app.tsx\n * import { registerAuthDecorator } from '@stackwright-pro/auth';\n *\n * registerAuthDecorator();\n *\n * function MyApp({ Component, pageProps }: AppProps) {\n * return (\n * <AuthProvider {...authProps}>\n * <Component {...pageProps} />\n * </AuthProvider>\n * );\n * }\n * ```\n */\nexport function registerAuthDecorator(): void {\n authDecoratorRegistry.decorator = withAuth;\n\n // Debug logging (only in browser with debug flag)\n if (typeof window !== 'undefined' && (window as any).__STACKWRIGHT_DEBUG__) {\n console.log('🔐 Auth decorator registered');\n }\n}\n\n/**\n * Get the registered auth decorator (for use by content renderers)\n *\n * Returns null if auth is not registered, allowing graceful degradation.\n * This is the function that OSS core would call to check if auth is available.\n *\n * @returns The withAuth decorator function, or null if not registered\n */\nexport function getAuthDecorator(): typeof withAuth | null {\n return authDecoratorRegistry.decorator;\n}\n\n/**\n * Wrap a component with auth if decorator is registered and config exists\n *\n * This is a safe wrapper that OSS packages can use without depending on auth.\n * If auth is not registered, returns the original component unchanged.\n * If auth config is missing/undefined, returns the original component unchanged.\n *\n * @example\n * ```tsx\n * // In content renderer (can live in OSS core!)\\n * function renderContentItem(item: ContentItem) {\n * const Component = getComponentFromRegistry(item.type);\n *\n * // Apply auth if available\n * const WrappedComponent = maybeWrapWithAuth(Component, item.auth);\n *\n * return <WrappedComponent {...item} />;\n * }\n * ```\n *\n * @param Component - Component to wrap\n * @param authConfig - Auth configuration from YAML (optional)\n * @returns Wrapped component if auth is registered, original component otherwise\n */\nexport function maybeWrapWithAuth<P extends { id?: string; [key: string]: any }>(\n Component: React.ComponentType<P>,\n authConfig?: ComponentAuthConfig\n): React.ComponentType<P> {\n const decorator = getAuthDecorator();\n\n // No decorator registered or no auth config = return unwrapped\n // This ensures graceful degradation when auth isn't installed\n if (!decorator || !authConfig) {\n return Component;\n }\n\n // Wrap with auth\n return decorator(Component, authConfig);\n}\n\n/**\n * Type guard to check if content item has auth config\n *\n * Useful for conditionally applying auth in renderers without\n * needing to import auth types.\n *\n * @example\n * ```tsx\n * if (hasAuthConfig(item)) {\n * // TypeScript knows item.auth exists\n * WrappedComponent = maybeWrapWithAuth(Component, item.auth);\n * }\n * ```\n */\nexport function hasAuthConfig(item: any): item is { auth: ComponentAuthConfig } {\n // Explicitly return boolean to handle null/undefined properly\n if (!item || typeof item !== 'object') {\n return false;\n }\n return 'auth' in item;\n}\n"]}
|