@quazardous/qdadm 1.19.3
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/LICENSE +21 -0
- package/README.md +111 -0
- package/package.json +101 -0
- package/src/assets/logo.svg +6 -0
- package/src/auth/SessionAuthAdapter.ts +459 -0
- package/src/auth/index.ts +15 -0
- package/src/chain/ActiveStack.ts +154 -0
- package/src/chain/StackHydrator.ts +292 -0
- package/src/chain/index.ts +25 -0
- package/src/chain/useActiveStack.ts +120 -0
- package/src/chain/useStackHydrator.ts +155 -0
- package/src/components/BoolCell.vue +34 -0
- package/src/components/InfoBanner.vue +73 -0
- package/src/components/QdadmRoot.vue +52 -0
- package/src/components/SeverityTag.vue +86 -0
- package/src/components/dialogs/BulkStatusDialog.vue +60 -0
- package/src/components/dialogs/MultiStepDialog.vue +331 -0
- package/src/components/dialogs/SimpleDialog.vue +122 -0
- package/src/components/dialogs/UnsavedChangesDialog.vue +80 -0
- package/src/components/display/BannerZone.vue +65 -0
- package/src/components/display/CardsGrid.vue +72 -0
- package/src/components/display/CopyableId.vue +84 -0
- package/src/components/display/EmptyState.vue +62 -0
- package/src/components/display/IntensityBar.vue +190 -0
- package/src/components/display/RichCardsGrid.vue +101 -0
- package/src/components/edit/FormActions.vue +78 -0
- package/src/components/edit/FormField.vue +82 -0
- package/src/components/edit/FormInput.vue +158 -0
- package/src/components/edit/FormPage.vue +260 -0
- package/src/components/edit/FormTab.vue +217 -0
- package/src/components/edit/FormTabs.vue +120 -0
- package/src/components/edit/LookupField.vue +406 -0
- package/src/components/edit/LookupPickerDialog.vue +230 -0
- package/src/components/editors/JsonEditorFoldable.vue +470 -0
- package/src/components/editors/JsonStructuredField.vue +264 -0
- package/src/components/editors/JsonViewer.vue +51 -0
- package/src/components/editors/KeyValueEditor.vue +314 -0
- package/src/components/editors/LanguageEditor.vue +259 -0
- package/src/components/editors/PermissionEditor.vue +558 -0
- package/src/components/editors/ScopeEditor.vue +372 -0
- package/src/components/editors/VanillaJsonEditor.vue +183 -0
- package/src/components/index.ts +83 -0
- package/src/components/item/FieldGroups.vue +392 -0
- package/src/components/layout/AppLayout.vue +1032 -0
- package/src/components/layout/BaseLayout.vue +190 -0
- package/src/components/layout/Breadcrumb.vue +55 -0
- package/src/components/layout/DashboardLayout.vue +104 -0
- package/src/components/layout/FormLayout.vue +256 -0
- package/src/components/layout/ListLayout.vue +278 -0
- package/src/components/layout/PageHeader.vue +156 -0
- package/src/components/layout/PageLayout.vue +126 -0
- package/src/components/layout/PageNav.vue +192 -0
- package/src/components/layout/SidebarBox.vue +87 -0
- package/src/components/layout/Zone.vue +182 -0
- package/src/components/layout/defaults/DefaultBreadcrumb.vue +149 -0
- package/src/components/layout/defaults/DefaultFooter.vue +89 -0
- package/src/components/layout/defaults/DefaultFormActions.vue +67 -0
- package/src/components/layout/defaults/DefaultHeader.vue +78 -0
- package/src/components/layout/defaults/DefaultMenu.vue +188 -0
- package/src/components/layout/defaults/DefaultPagination.vue +75 -0
- package/src/components/layout/defaults/DefaultTable.vue +137 -0
- package/src/components/layout/defaults/DefaultUserInfo.vue +111 -0
- package/src/components/layout/defaults/index.ts +16 -0
- package/src/components/lists/ActionButtons.vue +49 -0
- package/src/components/lists/ActionColumn.vue +40 -0
- package/src/components/lists/FilterBar.vue +54 -0
- package/src/components/lists/ListPage.vue +467 -0
- package/src/components/pages/LoginPage.vue +334 -0
- package/src/components/pages/NotFoundPage.vue +98 -0
- package/src/components/show/ShowDisplay.vue +343 -0
- package/src/components/show/ShowField.vue +67 -0
- package/src/components/show/ShowPage.vue +431 -0
- package/src/composables/index.ts +155 -0
- package/src/composables/useApp.ts +67 -0
- package/src/composables/useAuth.ts +98 -0
- package/src/composables/useBareForm.ts +319 -0
- package/src/composables/useBreadcrumb.ts +252 -0
- package/src/composables/useChildPage.ts +113 -0
- package/src/composables/useCurrentEntity.ts +60 -0
- package/src/composables/useDeferred.ts +94 -0
- package/src/composables/useDirtyState.ts +138 -0
- package/src/composables/useEntityItemFormPage.ts +942 -0
- package/src/composables/useEntityItemFormPage.types.ts +451 -0
- package/src/composables/useEntityItemPage.ts +438 -0
- package/src/composables/useEntityItemShowPage.ts +722 -0
- package/src/composables/useFieldManager.ts +692 -0
- package/src/composables/useForm.ts +544 -0
- package/src/composables/useGuardStore.ts +53 -0
- package/src/composables/useHooks.ts +56 -0
- package/src/composables/useInfoBanner.ts +217 -0
- package/src/composables/useJsonSyntax.ts +122 -0
- package/src/composables/useLayoutResolver.ts +329 -0
- package/src/composables/useListPage.ts +1535 -0
- package/src/composables/useListPage.types.ts +437 -0
- package/src/composables/useListPage.utils.ts +101 -0
- package/src/composables/useNavContext.ts +439 -0
- package/src/composables/useNavigation.ts +216 -0
- package/src/composables/useOptionsLookup.ts +323 -0
- package/src/composables/usePageTitle.ts +84 -0
- package/src/composables/useSSEBridge.ts +142 -0
- package/src/composables/useSemanticBreadcrumb.ts +222 -0
- package/src/composables/useSidebarState.ts +24 -0
- package/src/composables/useSignals.ts +52 -0
- package/src/composables/useUnsavedChangesGuard.ts +155 -0
- package/src/composables/useUserImpersonator.ts +317 -0
- package/src/composables/useZoneRegistry.ts +252 -0
- package/src/core/bundles.ts +558 -0
- package/src/core/decorator.ts +420 -0
- package/src/core/extension.ts +470 -0
- package/src/core/index.ts +59 -0
- package/src/deferred/DeferredRegistry.ts +353 -0
- package/src/deferred/index.ts +15 -0
- package/src/editors/index.ts +12 -0
- package/src/entity/EntityManager.cache.ts +530 -0
- package/src/entity/EntityManager.crud.ts +615 -0
- package/src/entity/EntityManager.interface.ts +78 -0
- package/src/entity/EntityManager.query.ts +221 -0
- package/src/entity/EntityManager.relations.ts +151 -0
- package/src/entity/EntityManager.ts +944 -0
- package/src/entity/EntityManager.types.ts +288 -0
- package/src/entity/auth/CompositeAuthAdapter.ts +194 -0
- package/src/entity/auth/EntityAuthAdapter.ts +119 -0
- package/src/entity/auth/PermissiveAdapter.ts +43 -0
- package/src/entity/auth/RoleHierarchy.ts +128 -0
- package/src/entity/auth/SecurityChecker.ts +202 -0
- package/src/entity/auth/factory.ts +203 -0
- package/src/entity/auth/index.ts +47 -0
- package/src/entity/factory.test.js +189 -0
- package/src/entity/factory.ts +146 -0
- package/src/entity/index.ts +49 -0
- package/src/entity/storage/ApiStorage.ts +228 -0
- package/src/entity/storage/IStorage.ts +100 -0
- package/src/entity/storage/LocalStorage.ts +200 -0
- package/src/entity/storage/MemoryStorage.ts +184 -0
- package/src/entity/storage/MockApiStorage.ts +318 -0
- package/src/entity/storage/SdkStorage.ts +377 -0
- package/src/entity/storage/factory.test.js +159 -0
- package/src/entity/storage/factory.ts +209 -0
- package/src/entity/storage/index.ts +75 -0
- package/src/gen/FieldMapper.ts +122 -0
- package/src/gen/StorageProfileFactory.ts +93 -0
- package/src/gen/connectors/BaseConnector.ts +150 -0
- package/src/gen/connectors/ManualConnector.test.js +499 -0
- package/src/gen/connectors/ManualConnector.ts +414 -0
- package/src/gen/connectors/OpenAPIConnector.test.js +836 -0
- package/src/gen/connectors/OpenAPIConnector.ts +693 -0
- package/src/gen/connectors/__fixtures__/sample-openapi.json +311 -0
- package/src/gen/connectors/index.ts +27 -0
- package/src/gen/createManagers.ts +245 -0
- package/src/gen/decorators.ts +134 -0
- package/src/gen/generateManagers.test.js +540 -0
- package/src/gen/generateManagers.ts +398 -0
- package/src/gen/index.ts +66 -0
- package/src/gen/schema.ts +248 -0
- package/src/gen/vite-plugin.ts +114 -0
- package/src/generated/managers/testManager.js +45 -0
- package/src/generated/managers/testManager.ts +46 -0
- package/src/hooks/HookRegistry.ts +16 -0
- package/src/hooks/index.ts +15 -0
- package/src/i18n/I18n.ts +344 -0
- package/src/i18n/IncrementalDomainProvider.ts +153 -0
- package/src/i18n/InlineTranslationProvider.ts +4 -0
- package/src/i18n/LazyTranslationProvider.ts +102 -0
- package/src/i18n/MessagesRegistry.ts +4 -0
- package/src/i18n/Resolver.ts +4 -0
- package/src/i18n/__tests__/I18n.test.ts +169 -0
- package/src/i18n/__tests__/IncrementalDomainProvider.test.ts +146 -0
- package/src/i18n/__tests__/LazyTranslationProvider.test.ts +100 -0
- package/src/i18n/__tests__/Resolver.test.ts +271 -0
- package/src/i18n/defaults/DefaultCoreProvider.ts +28 -0
- package/src/i18n/defaults/core.en.yml +55 -0
- package/src/i18n/defaults/core.fr.yml +55 -0
- package/src/i18n/index.ts +55 -0
- package/src/i18n/loaders/raw-modules.d.ts +15 -0
- package/src/i18n/loaders/yaml.ts +35 -0
- package/src/i18n/strategies.ts +4 -0
- package/src/i18n/types.ts +34 -0
- package/src/i18n/useI18n.ts +34 -0
- package/src/index.ts +453 -0
- package/src/kernel/EventRouter.ts +23 -0
- package/src/kernel/Kernel.api.ts +79 -0
- package/src/kernel/Kernel.auth.ts +120 -0
- package/src/kernel/Kernel.i18n.ts +29 -0
- package/src/kernel/Kernel.modules.ts +170 -0
- package/src/kernel/Kernel.registries.ts +237 -0
- package/src/kernel/Kernel.routing.ts +334 -0
- package/src/kernel/Kernel.ts +460 -0
- package/src/kernel/Kernel.types.ts +263 -0
- package/src/kernel/Kernel.vue.ts +304 -0
- package/src/kernel/KernelContext.entities.ts +80 -0
- package/src/kernel/KernelContext.events.ts +57 -0
- package/src/kernel/KernelContext.i18n.ts +37 -0
- package/src/kernel/KernelContext.permissions.ts +38 -0
- package/src/kernel/KernelContext.routing.ts +280 -0
- package/src/kernel/KernelContext.ts +252 -0
- package/src/kernel/KernelContext.types.ts +173 -0
- package/src/kernel/KernelContext.zones.ts +54 -0
- package/src/kernel/Module.ts +169 -0
- package/src/kernel/ModuleLoader.ts +645 -0
- package/src/kernel/SSEBridge.ts +11 -0
- package/src/kernel/SignalBus.ts +48 -0
- package/src/kernel/index.ts +56 -0
- package/src/module/index.ts +33 -0
- package/src/module/moduleRegistry.ts +492 -0
- package/src/modules/debug/AuthCollector.ts +501 -0
- package/src/modules/debug/Collector.ts +20 -0
- package/src/modules/debug/DebugBridge.ts +14 -0
- package/src/modules/debug/DebugModule.ts +220 -0
- package/src/modules/debug/EntitiesCollector.ts +839 -0
- package/src/modules/debug/ErrorCollector.ts +4 -0
- package/src/modules/debug/I18nCollector.ts +9 -0
- package/src/modules/debug/LocalStorageAdapter.ts +7 -0
- package/src/modules/debug/RouterCollector.ts +449 -0
- package/src/modules/debug/SignalCollector.ts +4 -0
- package/src/modules/debug/ToastCollector.ts +4 -0
- package/src/modules/debug/ZonesCollector.ts +447 -0
- package/src/modules/debug/components/DebugBar.vue +31 -0
- package/src/modules/debug/components/adminPanelsConfig.ts +42 -0
- package/src/modules/debug/components/index.ts +9 -0
- package/src/modules/debug/components/panels/AuthPanel.vue +267 -0
- package/src/modules/debug/components/panels/EntitiesPanel.vue +572 -0
- package/src/modules/debug/components/panels/I18nPanel.vue +738 -0
- package/src/modules/debug/components/panels/RouterPanel.vue +440 -0
- package/src/modules/debug/components/panels/ZonesPanel.vue +133 -0
- package/src/modules/debug/components/panels/index.ts +16 -0
- package/src/modules/debug/index.ts +58 -0
- package/src/modules/debug/styles.scss +2671 -0
- package/src/notifications/NotificationBadge.vue +35 -0
- package/src/notifications/NotificationListener.vue +71 -0
- package/src/notifications/NotificationModule.ts +76 -0
- package/src/notifications/NotificationPanel.vue +478 -0
- package/src/notifications/NotificationStore.ts +288 -0
- package/src/notifications/index.ts +42 -0
- package/src/notifications/styles.scss +32 -0
- package/src/orchestrator/Orchestrator.ts +427 -0
- package/src/orchestrator/index.ts +8 -0
- package/src/orchestrator/useOrchestrator.ts +80 -0
- package/src/plugin.ts +251 -0
- package/src/query/FilterQuery.ts +306 -0
- package/src/query/QueryExecutor.ts +341 -0
- package/src/query/index.ts +16 -0
- package/src/security/EntityRolesProvider.ts +385 -0
- package/src/security/PermissionMatcher.ts +152 -0
- package/src/security/PermissionRegistry.ts +273 -0
- package/src/security/PersistableRolesProvider.ts +584 -0
- package/src/security/RolesManager.ts +136 -0
- package/src/security/RolesProvider.ts +167 -0
- package/src/security/RolesProviderStorage.ts +211 -0
- package/src/security/SecurityModule.ts +94 -0
- package/src/security/StaticRolesProvider.ts +117 -0
- package/src/security/UsersManager.ts +163 -0
- package/src/security/index.ts +77 -0
- package/src/security/pages/RoleForm.vue +276 -0
- package/src/security/pages/RoleList.vue +123 -0
- package/src/styles/_alerts.scss +95 -0
- package/src/styles/_buttons.scss +391 -0
- package/src/styles/_cards.scss +241 -0
- package/src/styles/_code.scss +78 -0
- package/src/styles/_dialogs.scss +55 -0
- package/src/styles/_filter-bar.scss +70 -0
- package/src/styles/_forms.scss +434 -0
- package/src/styles/_helpers.scss +153 -0
- package/src/styles/_lists.scss +225 -0
- package/src/styles/_main.scss +356 -0
- package/src/styles/_markdown.scss +88 -0
- package/src/styles/_responsive.scss +255 -0
- package/src/styles/_show-pages.scss +186 -0
- package/src/styles/_sidebar.scss +380 -0
- package/src/styles/_states.scss +115 -0
- package/src/styles/_stats.scss +47 -0
- package/src/styles/_variables.scss +224 -0
- package/src/styles/index.scss +38 -0
- package/src/styles/theme/_components.scss +331 -0
- package/src/styles/theme/_index.scss +10 -0
- package/src/styles/theme/_tokens.scss +147 -0
- package/src/styles/theme/_utilities.scss +175 -0
- package/src/toast/ToastBridgeModule.ts +68 -0
- package/src/toast/ToastListener.vue +55 -0
- package/src/toast/index.ts +26 -0
- package/src/toast/useSignalToast.ts +154 -0
- package/src/types/index.ts +285 -0
- package/src/types/pluralize.d.ts +19 -0
- package/src/types/quarkernel.d.ts +50 -0
- package/src/utils/formatters.ts +174 -0
- package/src/utils/index.ts +35 -0
- package/src/utils/transformers.ts +131 -0
- package/src/vite/qdadmDebugPlugin.ts +401 -0
- package/src/vite-env.d.ts +50 -0
- package/src/zones/ZoneRegistry.ts +934 -0
- package/src/zones/index.ts +29 -0
- package/src/zones/zones.ts +191 -0
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AuthCollector - Debug collector for authentication state
|
|
3
|
+
*
|
|
4
|
+
* This collector displays current authentication information:
|
|
5
|
+
* - Current user details
|
|
6
|
+
* - Permissions/roles
|
|
7
|
+
* - Token info (expiry, claims)
|
|
8
|
+
* - Auth adapter status
|
|
9
|
+
*
|
|
10
|
+
* Only shows content when auth is configured in the application.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
Collector,
|
|
15
|
+
type CollectorContext,
|
|
16
|
+
type CollectorEntry,
|
|
17
|
+
type CollectorManifest,
|
|
18
|
+
type CollectorOptions,
|
|
19
|
+
type CollectorSnapshot,
|
|
20
|
+
} from './Collector'
|
|
21
|
+
import type { ISessionAuthAdapter, AuthUser } from '../../auth/SessionAuthAdapter'
|
|
22
|
+
import type { SecurityChecker } from '../../entity/auth/SecurityChecker'
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Auth event types
|
|
26
|
+
*/
|
|
27
|
+
export type AuthEventType = 'login' | 'logout' | 'impersonate' | 'impersonate-stop' | 'login-error'
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Auth event entry
|
|
31
|
+
*/
|
|
32
|
+
export interface AuthEvent {
|
|
33
|
+
type: AuthEventType
|
|
34
|
+
timestamp: Date
|
|
35
|
+
id: number
|
|
36
|
+
seen: boolean
|
|
37
|
+
data: unknown
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Auth entry types
|
|
42
|
+
*/
|
|
43
|
+
export interface AuthEntry extends CollectorEntry {
|
|
44
|
+
type: 'status' | 'user' | 'impersonated' | 'token' | 'user-permissions' | 'hierarchy' | 'role-permissions' | 'permissions' | 'adapter' | 'error'
|
|
45
|
+
label?: string
|
|
46
|
+
message?: string
|
|
47
|
+
data?: unknown
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Auth collector options
|
|
52
|
+
*/
|
|
53
|
+
export interface AuthCollectorOptions extends CollectorOptions {
|
|
54
|
+
eventTtl?: number
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Extended context for auth collector
|
|
59
|
+
*/
|
|
60
|
+
interface AuthCollectorContext extends CollectorContext {
|
|
61
|
+
auth?: ISessionAuthAdapter
|
|
62
|
+
authAdapter?: ISessionAuthAdapter
|
|
63
|
+
security?: SecurityChecker
|
|
64
|
+
permissionRegistry?: { size: number; getKeys: () => string[] }
|
|
65
|
+
_kernel?: { permissionRegistry?: { size: number; getKeys: () => string[] } }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Collector for authentication state visualization
|
|
70
|
+
*/
|
|
71
|
+
export class AuthCollector extends Collector<AuthEntry> {
|
|
72
|
+
static override collectorName = 'auth'
|
|
73
|
+
static override records = false
|
|
74
|
+
|
|
75
|
+
private _authAdapter: ISessionAuthAdapter | null = null
|
|
76
|
+
private _securityChecker: SecurityChecker | null = null
|
|
77
|
+
private _signalCleanups: Array<() => void> = []
|
|
78
|
+
private _recentEvents: AuthEvent[] = []
|
|
79
|
+
private _eventTtl: number
|
|
80
|
+
private _expiryTimer: ReturnType<typeof setTimeout> | null = null
|
|
81
|
+
|
|
82
|
+
constructor(options: AuthCollectorOptions = {}) {
|
|
83
|
+
super(options)
|
|
84
|
+
this._eventTtl = options.eventTtl ?? 60000
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
protected override _doInstall(ctx: CollectorContext): void {
|
|
88
|
+
this._ctx = ctx
|
|
89
|
+
const authCtx = ctx as AuthCollectorContext
|
|
90
|
+
this._authAdapter = authCtx.auth ?? authCtx.authAdapter ?? null
|
|
91
|
+
this._securityChecker = authCtx.security ?? null
|
|
92
|
+
this._setupSignals()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private _setupSignals(): void {
|
|
96
|
+
const signals = this._ctx?.signals
|
|
97
|
+
if (!signals) {
|
|
98
|
+
setTimeout(() => this._setupSignals(), 100)
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const loginCleanup = signals.on('auth:login', (event) => {
|
|
103
|
+
const data = (event.data ?? event) as { user?: AuthUser }
|
|
104
|
+
const user = data?.user ?? (this._authAdapter as { getUser?: () => AuthUser })?.getUser?.()
|
|
105
|
+
this._addEvent('login', { user })
|
|
106
|
+
})
|
|
107
|
+
this._signalCleanups.push(loginCleanup)
|
|
108
|
+
|
|
109
|
+
const logoutCleanup = signals.on('auth:logout', () => {
|
|
110
|
+
this._addEvent('logout')
|
|
111
|
+
})
|
|
112
|
+
this._signalCleanups.push(logoutCleanup)
|
|
113
|
+
|
|
114
|
+
const impersonateCleanup = signals.on('auth:impersonate', (payload) => {
|
|
115
|
+
this._addEvent('impersonate', payload.data)
|
|
116
|
+
})
|
|
117
|
+
this._signalCleanups.push(impersonateCleanup)
|
|
118
|
+
|
|
119
|
+
const impersonateStopCleanup = signals.on('auth:impersonate:stop', (payload) => {
|
|
120
|
+
this._addEvent('impersonate-stop', payload.data)
|
|
121
|
+
})
|
|
122
|
+
this._signalCleanups.push(impersonateStopCleanup)
|
|
123
|
+
|
|
124
|
+
const loginErrorCleanup = signals.on('auth:login:error', (payload) => {
|
|
125
|
+
this._addEvent('login-error', payload.data)
|
|
126
|
+
})
|
|
127
|
+
this._signalCleanups.push(loginErrorCleanup)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private _addEvent(type: AuthEventType, data: unknown = null): void {
|
|
131
|
+
this._recentEvents.unshift({
|
|
132
|
+
type,
|
|
133
|
+
timestamp: new Date(),
|
|
134
|
+
id: Date.now(),
|
|
135
|
+
seen: false,
|
|
136
|
+
data
|
|
137
|
+
})
|
|
138
|
+
this._scheduleExpiry()
|
|
139
|
+
this.notifyChange()
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private _scheduleExpiry(): void {
|
|
143
|
+
if (this._expiryTimer) {
|
|
144
|
+
clearTimeout(this._expiryTimer)
|
|
145
|
+
this._expiryTimer = null
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (this._recentEvents.length === 0) return
|
|
149
|
+
|
|
150
|
+
const now = Date.now()
|
|
151
|
+
const oldest = this._recentEvents[this._recentEvents.length - 1]
|
|
152
|
+
if (!oldest) return
|
|
153
|
+
const age = now - oldest.timestamp.getTime()
|
|
154
|
+
const delay = Math.max(100, this._eventTtl - age)
|
|
155
|
+
|
|
156
|
+
this._expiryTimer = setTimeout(() => {
|
|
157
|
+
this._expireOldEvents()
|
|
158
|
+
}, delay)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private _expireOldEvents(): void {
|
|
162
|
+
this._expiryTimer = null
|
|
163
|
+
const now = Date.now()
|
|
164
|
+
const before = this._recentEvents.length
|
|
165
|
+
this._recentEvents = this._recentEvents.filter(
|
|
166
|
+
e => (now - e.timestamp.getTime()) < this._eventTtl
|
|
167
|
+
)
|
|
168
|
+
if (this._recentEvents.length < before) {
|
|
169
|
+
this.notifyChange()
|
|
170
|
+
}
|
|
171
|
+
if (this._recentEvents.length > 0) {
|
|
172
|
+
this._scheduleExpiry()
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
protected override _doUninstall(): void {
|
|
177
|
+
if (this._expiryTimer) {
|
|
178
|
+
clearTimeout(this._expiryTimer)
|
|
179
|
+
this._expiryTimer = null
|
|
180
|
+
}
|
|
181
|
+
for (const cleanup of this._signalCleanups) {
|
|
182
|
+
if (typeof cleanup === 'function') cleanup()
|
|
183
|
+
}
|
|
184
|
+
this._signalCleanups = []
|
|
185
|
+
this._authAdapter = null
|
|
186
|
+
this._securityChecker = null
|
|
187
|
+
this._ctx = null
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
override getBadge(): number {
|
|
191
|
+
return this._recentEvents.filter(e => !e.seen).length
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
hasActivity(): boolean {
|
|
195
|
+
return this._recentEvents.some(e => !e.seen)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
getRecentEvents(): AuthEvent[] {
|
|
199
|
+
return this._recentEvents
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
getLastEvent(): AuthEventType | null {
|
|
203
|
+
return this._recentEvents[0]?.type ?? null
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
markSeen(): void {
|
|
207
|
+
for (const event of this._recentEvents) {
|
|
208
|
+
event.seen = true
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
clearEvents(): void {
|
|
213
|
+
this._recentEvents = []
|
|
214
|
+
this.notifyChange()
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
override getEntries(): AuthEntry[] {
|
|
218
|
+
const authCtx = this._ctx as AuthCollectorContext
|
|
219
|
+
const authAdapter = authCtx?.auth ?? authCtx?.authAdapter ?? this._authAdapter
|
|
220
|
+
if (!authAdapter) {
|
|
221
|
+
return [{ timestamp: Date.now(), type: 'status', message: 'No auth adapter configured' }]
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const entries: AuthEntry[] = []
|
|
225
|
+
const adapter = authAdapter as ISessionAuthAdapter & {
|
|
226
|
+
getUser?: () => AuthUser | null
|
|
227
|
+
getToken?: () => string | null
|
|
228
|
+
getPermissions?: () => string[]
|
|
229
|
+
isImpersonating?: () => boolean
|
|
230
|
+
getOriginalUser?: () => AuthUser | null
|
|
231
|
+
impersonate?: (user: AuthUser) => void
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// User info
|
|
235
|
+
try {
|
|
236
|
+
const user = adapter.getUser?.()
|
|
237
|
+
const securityChecker = authCtx?.security ?? this._securityChecker
|
|
238
|
+
|
|
239
|
+
if (user) {
|
|
240
|
+
const isImpersonating = adapter.isImpersonating?.() ?? false
|
|
241
|
+
const originalUser = isImpersonating ? adapter.getOriginalUser?.() : null
|
|
242
|
+
|
|
243
|
+
const realUser = originalUser ?? user
|
|
244
|
+
const realRoles = this._normalizeRoles((realUser as AuthUser & { role?: string }).roles ?? (realUser as AuthUser & { role?: string }).role)
|
|
245
|
+
const realPermissions = this._getEffectivePermissions(securityChecker, realRoles)
|
|
246
|
+
|
|
247
|
+
entries.push({
|
|
248
|
+
timestamp: Date.now(),
|
|
249
|
+
type: 'user',
|
|
250
|
+
label: 'Current User',
|
|
251
|
+
data: {
|
|
252
|
+
id: realUser.id,
|
|
253
|
+
username: realUser.username ?? realUser.name ?? realUser.email,
|
|
254
|
+
email: realUser.email,
|
|
255
|
+
roles: realRoles,
|
|
256
|
+
permissions: realPermissions
|
|
257
|
+
}
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
if (isImpersonating) {
|
|
261
|
+
const impersonatedRoles = this._normalizeRoles((user as AuthUser & { role?: string }).roles ?? (user as AuthUser & { role?: string }).role)
|
|
262
|
+
const impersonatedPermissions = this._getEffectivePermissions(securityChecker, impersonatedRoles)
|
|
263
|
+
|
|
264
|
+
entries.push({
|
|
265
|
+
timestamp: Date.now(),
|
|
266
|
+
type: 'impersonated',
|
|
267
|
+
label: 'Impersonated User',
|
|
268
|
+
data: {
|
|
269
|
+
id: user.id,
|
|
270
|
+
username: user.username ?? user.name ?? user.email,
|
|
271
|
+
roles: impersonatedRoles,
|
|
272
|
+
permissions: impersonatedPermissions
|
|
273
|
+
}
|
|
274
|
+
})
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
const checker = securityChecker as SecurityChecker & { rolesProvider?: { getAnonymousRole?: () => string } }
|
|
278
|
+
const anonymousRole = checker?.rolesProvider?.getAnonymousRole?.() ?? 'ROLE_ANONYMOUS'
|
|
279
|
+
const effectivePermissions = this._getEffectivePermissions(securityChecker, [anonymousRole])
|
|
280
|
+
|
|
281
|
+
entries.push({
|
|
282
|
+
timestamp: Date.now(),
|
|
283
|
+
type: 'user',
|
|
284
|
+
label: 'Anonymous',
|
|
285
|
+
data: {
|
|
286
|
+
role: anonymousRole,
|
|
287
|
+
authenticated: false,
|
|
288
|
+
permissions: effectivePermissions
|
|
289
|
+
}
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
} catch (e) {
|
|
293
|
+
entries.push({
|
|
294
|
+
timestamp: Date.now(),
|
|
295
|
+
type: 'error',
|
|
296
|
+
label: 'User Error',
|
|
297
|
+
message: (e as Error).message
|
|
298
|
+
})
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Token info
|
|
302
|
+
try {
|
|
303
|
+
const token = adapter.getToken?.()
|
|
304
|
+
if (token) {
|
|
305
|
+
const decoded = this._decodeToken(token)
|
|
306
|
+
entries.push({
|
|
307
|
+
timestamp: Date.now(),
|
|
308
|
+
type: 'token',
|
|
309
|
+
label: 'Token',
|
|
310
|
+
data: {
|
|
311
|
+
preview: token.substring(0, 20) + '...',
|
|
312
|
+
expiresAt: decoded?.exp ? new Date(decoded.exp * 1000).toLocaleString() : 'Unknown',
|
|
313
|
+
issuedAt: decoded?.iat ? new Date(decoded.iat * 1000).toLocaleString() : 'Unknown',
|
|
314
|
+
claims: decoded
|
|
315
|
+
}
|
|
316
|
+
})
|
|
317
|
+
}
|
|
318
|
+
} catch {
|
|
319
|
+
// Token not available
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// User permissions
|
|
323
|
+
try {
|
|
324
|
+
const userPermissions = adapter.getPermissions?.()
|
|
325
|
+
if (userPermissions && userPermissions.length > 0) {
|
|
326
|
+
entries.push({
|
|
327
|
+
timestamp: Date.now(),
|
|
328
|
+
type: 'user-permissions',
|
|
329
|
+
label: 'User Permissions',
|
|
330
|
+
data: userPermissions
|
|
331
|
+
})
|
|
332
|
+
}
|
|
333
|
+
} catch {
|
|
334
|
+
// Permissions not available
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Role hierarchy & permissions
|
|
338
|
+
try {
|
|
339
|
+
const securityChecker = this._securityChecker ?? authCtx?.security
|
|
340
|
+
const checker = securityChecker as SecurityChecker & { roleHierarchy?: { map: Record<string, string[]> }; rolePermissions?: Record<string, string[]> }
|
|
341
|
+
const hierarchy = checker?.roleHierarchy?.map
|
|
342
|
+
if (hierarchy && Object.keys(hierarchy).length > 0) {
|
|
343
|
+
entries.push({
|
|
344
|
+
timestamp: Date.now(),
|
|
345
|
+
type: 'hierarchy',
|
|
346
|
+
label: 'Role Hierarchy',
|
|
347
|
+
data: hierarchy
|
|
348
|
+
})
|
|
349
|
+
}
|
|
350
|
+
const rolePermissions = checker?.rolePermissions
|
|
351
|
+
if (rolePermissions && Object.keys(rolePermissions).length > 0) {
|
|
352
|
+
entries.push({
|
|
353
|
+
timestamp: Date.now(),
|
|
354
|
+
type: 'role-permissions',
|
|
355
|
+
label: 'Role Permissions',
|
|
356
|
+
data: rolePermissions
|
|
357
|
+
})
|
|
358
|
+
}
|
|
359
|
+
} catch {
|
|
360
|
+
// Security checker not available
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Registered permissions
|
|
364
|
+
try {
|
|
365
|
+
const permissionRegistry = authCtx?.permissionRegistry
|
|
366
|
+
?? authCtx?._kernel?.permissionRegistry
|
|
367
|
+
if (permissionRegistry && permissionRegistry.size > 0) {
|
|
368
|
+
const permissions = permissionRegistry.getKeys()
|
|
369
|
+
entries.push({
|
|
370
|
+
timestamp: Date.now(),
|
|
371
|
+
type: 'permissions',
|
|
372
|
+
label: 'Permissions',
|
|
373
|
+
data: permissions
|
|
374
|
+
})
|
|
375
|
+
}
|
|
376
|
+
} catch (e) {
|
|
377
|
+
console.warn('[AuthCollector] Error accessing permissionRegistry:', e)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Adapter info
|
|
381
|
+
entries.push({
|
|
382
|
+
timestamp: Date.now(),
|
|
383
|
+
type: 'adapter',
|
|
384
|
+
label: 'Auth Adapter',
|
|
385
|
+
data: {
|
|
386
|
+
type: adapter.constructor?.name ?? 'Unknown',
|
|
387
|
+
hasUser: !!adapter.getUser,
|
|
388
|
+
hasToken: !!adapter.getToken,
|
|
389
|
+
hasPermissions: !!adapter.getPermissions,
|
|
390
|
+
hasLogin: !!(adapter as { login?: unknown }).login,
|
|
391
|
+
hasLogout: !!(adapter as { logout?: unknown }).logout,
|
|
392
|
+
hasImpersonate: !!adapter.impersonate,
|
|
393
|
+
isImpersonating: adapter.isImpersonating?.() ?? false
|
|
394
|
+
}
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
return entries
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
private _getEffectivePermissions(securityChecker: SecurityChecker | null | undefined, roles: string[]): string[] {
|
|
401
|
+
if (!securityChecker || !roles || roles.length === 0) {
|
|
402
|
+
return []
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
try {
|
|
406
|
+
const checker = securityChecker as SecurityChecker & {
|
|
407
|
+
getUserPermissions?: (user: { roles: string[] }) => string[]
|
|
408
|
+
rolesProvider?: { getPermissions?: (role: string) => string[] }
|
|
409
|
+
roleHierarchy?: { getReachableRoles?: (role: string) => string[] }
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (checker.getUserPermissions) {
|
|
413
|
+
const mockUser = { roles }
|
|
414
|
+
const perms = checker.getUserPermissions(mockUser)
|
|
415
|
+
return [...new Set(perms)].sort()
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const rolesProvider = checker.rolesProvider
|
|
419
|
+
if (!rolesProvider) return []
|
|
420
|
+
|
|
421
|
+
const permissions = new Set<string>()
|
|
422
|
+
for (const role of roles) {
|
|
423
|
+
const reachable = checker.roleHierarchy?.getReachableRoles?.(role) ?? [role]
|
|
424
|
+
for (const r of reachable) {
|
|
425
|
+
const rolePerms = rolesProvider.getPermissions?.(r) ?? []
|
|
426
|
+
for (const perm of rolePerms) {
|
|
427
|
+
permissions.add(perm)
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return [...permissions].sort()
|
|
433
|
+
} catch (e) {
|
|
434
|
+
console.warn('[AuthCollector] Error getting effective permissions:', e)
|
|
435
|
+
return []
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
private _normalizeRoles(roles: string | string[] | undefined): string[] {
|
|
440
|
+
if (!roles) return []
|
|
441
|
+
const arr = Array.isArray(roles) ? roles : [roles]
|
|
442
|
+
return arr.map(role => {
|
|
443
|
+
if (!role) return null
|
|
444
|
+
const upper = role.toUpperCase()
|
|
445
|
+
return upper.startsWith('ROLE_') ? upper : `ROLE_${upper}`
|
|
446
|
+
}).filter((r): r is string => r !== null)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
private _decodeToken(token: string): { exp?: number; iat?: number; [key: string]: unknown } | null {
|
|
450
|
+
try {
|
|
451
|
+
const parts = token.split('.')
|
|
452
|
+
if (parts.length !== 3) return null
|
|
453
|
+
const payload = parts[1]
|
|
454
|
+
if (!payload) return null
|
|
455
|
+
const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/'))
|
|
456
|
+
return JSON.parse(decoded)
|
|
457
|
+
} catch {
|
|
458
|
+
return null
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
override describe(): CollectorManifest {
|
|
463
|
+
return {
|
|
464
|
+
name: this.name,
|
|
465
|
+
records: false,
|
|
466
|
+
summary:
|
|
467
|
+
'Surfaces current authentication state: user, roles, permissions, hierarchy, token claims, adapter capabilities, plus a TTL-bounded log of recent auth events.',
|
|
468
|
+
entryShape: {
|
|
469
|
+
type: 'string (user|impersonated|token|user-permissions|hierarchy|role-permissions|permissions|adapter|status|error)',
|
|
470
|
+
label: 'string?',
|
|
471
|
+
message: 'string?',
|
|
472
|
+
data: 'json',
|
|
473
|
+
},
|
|
474
|
+
stateShape: {
|
|
475
|
+
recentEvents: 'AuthEvent[] (TTL-bounded)',
|
|
476
|
+
lastEvent: 'string?',
|
|
477
|
+
},
|
|
478
|
+
actions: this._builtinActionManifests(),
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
override snapshot(): CollectorSnapshot {
|
|
483
|
+
const entries = this.getEntries()
|
|
484
|
+
return {
|
|
485
|
+
name: this.name,
|
|
486
|
+
entries,
|
|
487
|
+
count: entries.length,
|
|
488
|
+
unseen: this.getBadge(),
|
|
489
|
+
state: {
|
|
490
|
+
recentEvents: this._recentEvents.map((e) => ({
|
|
491
|
+
type: e.type,
|
|
492
|
+
timestamp: e.timestamp.toISOString(),
|
|
493
|
+
id: e.id,
|
|
494
|
+
seen: e.seen,
|
|
495
|
+
data: e.data,
|
|
496
|
+
})),
|
|
497
|
+
lastEvent: this.getLastEvent(),
|
|
498
|
+
},
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collector — qdadm re-export.
|
|
3
|
+
*
|
|
4
|
+
* The actual implementation now lives in `@quazardous/qddebug` so it can be
|
|
5
|
+
* shared with qdcms. Existing internal imports (`from './Collector'`)
|
|
6
|
+
* continue to work via this shim.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
Collector,
|
|
11
|
+
type CollectorEntry,
|
|
12
|
+
type CollectorContext,
|
|
13
|
+
type CollectorOptions,
|
|
14
|
+
type CollectorManifest,
|
|
15
|
+
type CollectorSnapshot,
|
|
16
|
+
type CollectorAction,
|
|
17
|
+
type CollectorActionManifest,
|
|
18
|
+
type DebugBridgeInterface,
|
|
19
|
+
type NotifyCallback,
|
|
20
|
+
} from '@quazardous/qddebug'
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DebugBridge — qdadm re-export.
|
|
3
|
+
*
|
|
4
|
+
* The actual implementation now lives in `@quazardous/qddebug` so it can be
|
|
5
|
+
* shared with qdcms.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
DebugBridge,
|
|
10
|
+
createDebugBridge,
|
|
11
|
+
type DebugBridgeOptions,
|
|
12
|
+
type BridgeManifest,
|
|
13
|
+
type BridgeSnapshot,
|
|
14
|
+
} from '@quazardous/qddebug'
|