@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 quazardous
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# qdadm
|
|
2
|
+
|
|
3
|
+
**Vue 3 admin framework. PrimeVue. Zero boilerplate.**
|
|
4
|
+
|
|
5
|
+
Quick start: [../../README.md](../../README.md) (5 min tutorial)
|
|
6
|
+
|
|
7
|
+
Concepts & patterns: [QDADM_CREDO.md](QDADM_CREDO.md)
|
|
8
|
+
|
|
9
|
+
Changelog: [../../CHANGELOG.md](../../CHANGELOG.md)
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @quazardous/qdadm primevue @primeuix/themes
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Exports
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
// Core
|
|
21
|
+
import { Kernel, EntityManager } from '@quazardous/qdadm'
|
|
22
|
+
|
|
23
|
+
// Storage backends
|
|
24
|
+
import { MockApiStorage, ApiStorage, SdkStorage } from '@quazardous/qdadm'
|
|
25
|
+
|
|
26
|
+
// Auth
|
|
27
|
+
import { SessionAuthAdapter, LocalStorageSessionAuthAdapter } from '@quazardous/qdadm'
|
|
28
|
+
|
|
29
|
+
// Security (permissions, roles)
|
|
30
|
+
import { SecurityChecker, PermissionMatcher, PermissionRegistry } from '@quazardous/qdadm/security'
|
|
31
|
+
import { PersistableRoleProvider, createLocalStorageRolesProvider } from '@quazardous/qdadm/security'
|
|
32
|
+
|
|
33
|
+
// Composables
|
|
34
|
+
import { useForm, useBareForm, useListPageBuilder } from '@quazardous/qdadm/composables'
|
|
35
|
+
|
|
36
|
+
// Components
|
|
37
|
+
import { ListPage, PageLayout, AppLayout, FormField, FormActions } from '@quazardous/qdadm/components'
|
|
38
|
+
|
|
39
|
+
// Module system
|
|
40
|
+
import { KernelContext } from '@quazardous/qdadm/module'
|
|
41
|
+
|
|
42
|
+
// Styles
|
|
43
|
+
import '@quazardous/qdadm/styles'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## ctx.crud() - Route Helper
|
|
47
|
+
|
|
48
|
+
Register CRUD routes + navigation in one call:
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
class BooksModule extends Module {
|
|
52
|
+
async connect(ctx) {
|
|
53
|
+
ctx.entity('books', { ... })
|
|
54
|
+
|
|
55
|
+
// Full CRUD with single form
|
|
56
|
+
ctx.crud('books', {
|
|
57
|
+
list: () => import('./pages/BookList.vue'),
|
|
58
|
+
form: () => import('./pages/BookForm.vue') // create + edit
|
|
59
|
+
}, {
|
|
60
|
+
nav: { section: 'Library', icon: 'pi pi-book' }
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
// List only (read-only entity)
|
|
64
|
+
ctx.crud('settings', {
|
|
65
|
+
list: () => import('./pages/SettingsPage.vue')
|
|
66
|
+
}, { nav: { section: 'Config', icon: 'pi pi-cog' } })
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Auto-generates: routes, route family, nav item. Use `ctx.routes()` for custom pages.
|
|
72
|
+
|
|
73
|
+
## SdkStorage
|
|
74
|
+
|
|
75
|
+
Adapter for generated SDK clients (hey-api, openapi-generator, etc.):
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
import { SdkStorage } from '@quazardous/qdadm'
|
|
79
|
+
|
|
80
|
+
const storage = new SdkStorage({
|
|
81
|
+
sdk,
|
|
82
|
+
methods: {
|
|
83
|
+
list: 'getApiBooks',
|
|
84
|
+
get: 'getApiBooksById',
|
|
85
|
+
create: 'postApiBooks',
|
|
86
|
+
update: 'patchApiBooksById',
|
|
87
|
+
delete: 'deleteApiBooksById'
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Options: `transformRequest`, `transformResponse`, `responseFormat`, `clientSidePagination`.
|
|
93
|
+
|
|
94
|
+
## Documentation
|
|
95
|
+
|
|
96
|
+
| Doc | Purpose |
|
|
97
|
+
|-----|---------|
|
|
98
|
+
| [QDADM_CREDO](QDADM_CREDO.md) | Philosophy, patterns, concepts |
|
|
99
|
+
| [Architecture](../../docs/architecture.md) | PAC pattern, layers |
|
|
100
|
+
| [Extension](../../docs/extension.md) | Hooks, signals, zones |
|
|
101
|
+
|
|
102
|
+
## Peer Dependencies
|
|
103
|
+
|
|
104
|
+
- vue ^3.3.0
|
|
105
|
+
- vue-router ^4.0.0
|
|
106
|
+
- primevue ^4.0.0
|
|
107
|
+
- pinia ^2.0.0
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@quazardous/qdadm",
|
|
3
|
+
"version": "1.19.3",
|
|
4
|
+
"description": "Vue 3 framework for admin dashboards with PrimeVue",
|
|
5
|
+
"author": "quazardous",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/quazardous/qdadm.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/quazardous/qdadm#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/quazardous/qdadm/issues"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "src/index.ts",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"test:watch": "vitest",
|
|
20
|
+
"type-check": "vue-tsc --noEmit",
|
|
21
|
+
"lint": "eslint src/",
|
|
22
|
+
"lint:fix": "eslint src/ --fix",
|
|
23
|
+
"format": "prettier --write src/",
|
|
24
|
+
"build:types": "vue-tsc --declaration --emitDeclarationOnly --outDir dist/types"
|
|
25
|
+
},
|
|
26
|
+
"exports": {
|
|
27
|
+
".": "./src/index.ts",
|
|
28
|
+
"./auth": "./src/auth/index.ts",
|
|
29
|
+
"./security": "./src/security/index.ts",
|
|
30
|
+
"./composables": "./src/composables/index.ts",
|
|
31
|
+
"./components": "./src/components/index.ts",
|
|
32
|
+
"./editors": "./src/editors/index.ts",
|
|
33
|
+
"./module": "./src/module/index.ts",
|
|
34
|
+
"./utils": "./src/utils/index.ts",
|
|
35
|
+
"./modules/debug": "./src/modules/debug/index.ts",
|
|
36
|
+
"./styles": "./src/styles/index.scss",
|
|
37
|
+
"./styles/variables": "./src/styles/_variables.scss",
|
|
38
|
+
"./gen": "./src/gen/index.ts",
|
|
39
|
+
"./gen/vite-plugin": "./src/gen/vite-plugin.ts",
|
|
40
|
+
"./vite-plugin-debug": "./src/vite/qdadmDebugPlugin.ts"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"src",
|
|
44
|
+
"README.md",
|
|
45
|
+
"LICENSE"
|
|
46
|
+
],
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@quazardous/qdcore": "^0.2.1",
|
|
49
|
+
"@quazardous/qddebug": "^0.2.1",
|
|
50
|
+
"@quazardous/quarkernel": "^2.1.0",
|
|
51
|
+
"pluralize": "^8.0.0",
|
|
52
|
+
"yaml": "^2.8.4"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"pinia": "^2.0.0 || ^3.0.0",
|
|
56
|
+
"primevue": "^4.0.0",
|
|
57
|
+
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0",
|
|
58
|
+
"vue": "^3.3.0",
|
|
59
|
+
"vue-router": "^4.0.0"
|
|
60
|
+
},
|
|
61
|
+
"peerDependenciesMeta": {
|
|
62
|
+
"vanilla-jsoneditor": {
|
|
63
|
+
"optional": true
|
|
64
|
+
},
|
|
65
|
+
"vite": {
|
|
66
|
+
"optional": true
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"optionalDependencies": {
|
|
70
|
+
"vanilla-jsoneditor": "^0.23.0"
|
|
71
|
+
},
|
|
72
|
+
"keywords": [
|
|
73
|
+
"vue",
|
|
74
|
+
"vue3",
|
|
75
|
+
"admin",
|
|
76
|
+
"dashboard",
|
|
77
|
+
"framework",
|
|
78
|
+
"primevue",
|
|
79
|
+
"crud",
|
|
80
|
+
"entity-manager"
|
|
81
|
+
],
|
|
82
|
+
"devDependencies": {
|
|
83
|
+
"@eslint/js": "^9.39.2",
|
|
84
|
+
"@types/node": "^25.0.6",
|
|
85
|
+
"@vitejs/plugin-vue": "^6.0.0",
|
|
86
|
+
"@vue/test-utils": "^2.4.6",
|
|
87
|
+
"@vue/tsconfig": "^0.8.1",
|
|
88
|
+
"eslint": "^9.39.2",
|
|
89
|
+
"eslint-config-prettier": "^10.1.8",
|
|
90
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
91
|
+
"eslint-plugin-vue": "^10.6.2",
|
|
92
|
+
"globals": "^17.0.0",
|
|
93
|
+
"jsdom": "^25.0.1",
|
|
94
|
+
"prettier": "^3.7.4",
|
|
95
|
+
"sass": "^1.97.1",
|
|
96
|
+
"typescript": "^5.9.3",
|
|
97
|
+
"typescript-eslint": "^8.52.0",
|
|
98
|
+
"vitest": "^2.1.8",
|
|
99
|
+
"vue-tsc": "^3.2.2"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100">
|
|
2
|
+
<polygon points="50,5 93,27.5 93,72.5 50,95 7,72.5 7,27.5" fill="#1E3A8A"/>
|
|
3
|
+
<text x="48" y="50" text-anchor="middle" dominant-baseline="central" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="58" font-weight="800" letter-spacing="-4">
|
|
4
|
+
<tspan fill="#60A5FA">Q</tspan><tspan fill="#93C5FD">D</tspan>
|
|
5
|
+
</text>
|
|
6
|
+
</svg>
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SessionAuthAdapter - Base class for user authentication
|
|
3
|
+
*
|
|
4
|
+
* Applications extend this class to implement their authentication logic.
|
|
5
|
+
* The adapter handles user sessions: login, logout, token management.
|
|
6
|
+
*
|
|
7
|
+
* This is different from EntityAuthAdapter which handles entity-level permissions.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* class MyAuthAdapter extends SessionAuthAdapter {
|
|
12
|
+
* async login({ username, password }: LoginCredentials) {
|
|
13
|
+
* const response = await api.post('/auth/login', { username, password })
|
|
14
|
+
* this.setSession(response.token, response.user)
|
|
15
|
+
* return { token: response.token, user: response.user }
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* logout() {
|
|
19
|
+
* this.clearSession()
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @abstract
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import type { SignalBus } from '../kernel/SignalBus'
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Login credentials interface
|
|
31
|
+
*/
|
|
32
|
+
export interface LoginCredentials {
|
|
33
|
+
username: string
|
|
34
|
+
password: string
|
|
35
|
+
[key: string]: unknown
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Session data returned from login
|
|
40
|
+
*/
|
|
41
|
+
export interface SessionData<TUser = AuthUser> {
|
|
42
|
+
token: string
|
|
43
|
+
user: TUser
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Generic user object interface
|
|
48
|
+
*/
|
|
49
|
+
export interface AuthUser {
|
|
50
|
+
id?: string | number
|
|
51
|
+
username?: string
|
|
52
|
+
email?: string
|
|
53
|
+
name?: string
|
|
54
|
+
roles?: string[]
|
|
55
|
+
[key: string]: unknown
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Interface for session authentication adapters
|
|
60
|
+
*/
|
|
61
|
+
export interface ISessionAuthAdapter<TUser = AuthUser> {
|
|
62
|
+
login(credentials: LoginCredentials): Promise<SessionData<TUser>>
|
|
63
|
+
logout(): void
|
|
64
|
+
isAuthenticated(): boolean
|
|
65
|
+
getToken(): string | null
|
|
66
|
+
getUser(): TUser | null
|
|
67
|
+
readonly user: TUser | null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Base class for session authentication
|
|
72
|
+
*/
|
|
73
|
+
export class SessionAuthAdapter<TUser extends AuthUser = AuthUser> implements ISessionAuthAdapter<TUser> {
|
|
74
|
+
/**
|
|
75
|
+
* Internal session state
|
|
76
|
+
* @protected
|
|
77
|
+
*/
|
|
78
|
+
protected _token: string | null = null
|
|
79
|
+
protected _user: TUser | null = null
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Authenticate user with credentials
|
|
83
|
+
*
|
|
84
|
+
* @param credentials - Login credentials
|
|
85
|
+
* @returns Session data
|
|
86
|
+
* @throws Error If authentication fails
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* const { token, user } = await adapter.login({ username: 'admin', password: 'secret' })
|
|
90
|
+
*/
|
|
91
|
+
async login(_credentials: LoginCredentials): Promise<SessionData<TUser>> {
|
|
92
|
+
throw new Error('[SessionAuthAdapter] login() must be implemented by subclass')
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* End the current session
|
|
97
|
+
*
|
|
98
|
+
* Should clear all session data (tokens, user info).
|
|
99
|
+
* Called by AppLayout logout button and useAuth().logout()
|
|
100
|
+
*/
|
|
101
|
+
logout(): void {
|
|
102
|
+
throw new Error('[SessionAuthAdapter] logout() must be implemented by subclass')
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Check if user is currently authenticated
|
|
107
|
+
*
|
|
108
|
+
* @returns True if user has valid session
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* if (adapter.isAuthenticated()) {
|
|
112
|
+
* // Show dashboard
|
|
113
|
+
* } else {
|
|
114
|
+
* // Redirect to login
|
|
115
|
+
* }
|
|
116
|
+
*/
|
|
117
|
+
isAuthenticated(): boolean {
|
|
118
|
+
throw new Error('[SessionAuthAdapter] isAuthenticated() must be implemented by subclass')
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get the current authentication token
|
|
123
|
+
*
|
|
124
|
+
* Used by API clients to include in request headers.
|
|
125
|
+
*
|
|
126
|
+
* @returns JWT token or null if not authenticated
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* const token = adapter.getToken()
|
|
130
|
+
* fetch('/api/data', {
|
|
131
|
+
* headers: { Authorization: `Bearer ${token}` }
|
|
132
|
+
* })
|
|
133
|
+
*/
|
|
134
|
+
getToken(): string | null {
|
|
135
|
+
throw new Error('[SessionAuthAdapter] getToken() must be implemented by subclass')
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get the current user object
|
|
140
|
+
*
|
|
141
|
+
* Returns user data from the session. The shape depends on your backend.
|
|
142
|
+
*
|
|
143
|
+
* @returns User object or null if not authenticated
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* const user = adapter.getUser()
|
|
147
|
+
* console.log(user.username, user.email, user.roles)
|
|
148
|
+
*/
|
|
149
|
+
getUser(): TUser | null {
|
|
150
|
+
throw new Error('[SessionAuthAdapter] getUser() must be implemented by subclass')
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Synchronous user getter (optional)
|
|
155
|
+
*
|
|
156
|
+
* Some implementations prefer a property instead of method.
|
|
157
|
+
* useAuth() supports both patterns.
|
|
158
|
+
*/
|
|
159
|
+
get user(): TUser | null {
|
|
160
|
+
return this.getUser()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ─────────────────────────────────────────────────────────────────
|
|
164
|
+
// Helper methods for subclasses
|
|
165
|
+
// ─────────────────────────────────────────────────────────────────
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Set session data (helper for subclasses)
|
|
169
|
+
*
|
|
170
|
+
* @param token - Authentication token
|
|
171
|
+
* @param user - User object
|
|
172
|
+
* @protected
|
|
173
|
+
*/
|
|
174
|
+
protected setSession(token: string, user: TUser): void {
|
|
175
|
+
this._token = token
|
|
176
|
+
this._user = user
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Clear session data (helper for subclasses)
|
|
181
|
+
*
|
|
182
|
+
* @protected
|
|
183
|
+
*/
|
|
184
|
+
protected clearSession(): void {
|
|
185
|
+
this._token = null
|
|
186
|
+
this._user = null
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Destroy session (catastrophic/silent logout)
|
|
191
|
+
*
|
|
192
|
+
* Clears token WITHOUT triggering normal logout signals.
|
|
193
|
+
* Used by DebugBar to test how the app handles unexpected session loss.
|
|
194
|
+
*/
|
|
195
|
+
destroySession(): void {
|
|
196
|
+
this._token = null
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Validate that the adapter is properly configured
|
|
201
|
+
*
|
|
202
|
+
* Called during bootstrap to catch configuration errors early.
|
|
203
|
+
*
|
|
204
|
+
* @throws Error If required methods are not implemented
|
|
205
|
+
*/
|
|
206
|
+
static validate(adapter: ISessionAuthAdapter): void {
|
|
207
|
+
const required: (keyof ISessionAuthAdapter)[] = ['login', 'logout', 'isAuthenticated', 'getToken', 'getUser']
|
|
208
|
+
const missing = required.filter(method => typeof adapter[method] !== 'function')
|
|
209
|
+
|
|
210
|
+
if (missing.length > 0) {
|
|
211
|
+
throw new Error(
|
|
212
|
+
`[SessionAuthAdapter] Missing required methods: ${missing.join(', ')}\n` +
|
|
213
|
+
'Ensure your authAdapter implements all required methods or extends SessionAuthAdapter.'
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Stored session data structure
|
|
221
|
+
*/
|
|
222
|
+
interface StoredSessionData<TUser = AuthUser> {
|
|
223
|
+
token: string
|
|
224
|
+
user: TUser
|
|
225
|
+
originalUser?: TUser
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Configuration options for LocalStorageSessionAuthAdapter
|
|
230
|
+
*/
|
|
231
|
+
export interface LocalStorageAuthConfig {
|
|
232
|
+
/** localStorage key for session data (default: 'qdadm_auth') */
|
|
233
|
+
storageKey?: string
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* LocalStorage-based SessionAuthAdapter implementation
|
|
238
|
+
*
|
|
239
|
+
* Ready-to-use adapter that stores session in localStorage.
|
|
240
|
+
* Extend and override login() to add your API call.
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```ts
|
|
244
|
+
* class MyAuthAdapter extends LocalStorageSessionAuthAdapter {
|
|
245
|
+
* constructor() {
|
|
246
|
+
* super('my_app_auth') // localStorage key
|
|
247
|
+
* }
|
|
248
|
+
*
|
|
249
|
+
* async login({ username, password }: LoginCredentials) {
|
|
250
|
+
* const res = await fetch('/api/login', {
|
|
251
|
+
* method: 'POST',
|
|
252
|
+
* body: JSON.stringify({ username, password })
|
|
253
|
+
* })
|
|
254
|
+
* const data = await res.json()
|
|
255
|
+
* this.setSession(data.token, data.user)
|
|
256
|
+
* this.persist()
|
|
257
|
+
* return data
|
|
258
|
+
* }
|
|
259
|
+
* }
|
|
260
|
+
* ```
|
|
261
|
+
*/
|
|
262
|
+
export class LocalStorageSessionAuthAdapter<TUser extends AuthUser = AuthUser> extends SessionAuthAdapter<TUser> {
|
|
263
|
+
/**
|
|
264
|
+
* Default configuration. Override to change defaults globally:
|
|
265
|
+
* @example
|
|
266
|
+
* LocalStorageSessionAuthAdapter.defaults.storageKey = 'my_app_auth'
|
|
267
|
+
*/
|
|
268
|
+
static defaults: Required<LocalStorageAuthConfig> = {
|
|
269
|
+
storageKey: 'qdadm_auth'
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
protected _storageKey: string
|
|
273
|
+
protected _originalUser: TUser | null = null // Stores original user during impersonation
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* @param config - Configuration options or storage key string (for backward compatibility)
|
|
277
|
+
*/
|
|
278
|
+
constructor(config?: LocalStorageAuthConfig | string) {
|
|
279
|
+
super()
|
|
280
|
+
// Support both string (legacy) and config object
|
|
281
|
+
if (typeof config === 'string') {
|
|
282
|
+
this._storageKey = config
|
|
283
|
+
} else {
|
|
284
|
+
this._storageKey = config?.storageKey ?? LocalStorageSessionAuthAdapter.defaults.storageKey
|
|
285
|
+
}
|
|
286
|
+
this._restore()
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Restore session from localStorage on init
|
|
291
|
+
* @private
|
|
292
|
+
*/
|
|
293
|
+
private _restore(): void {
|
|
294
|
+
try {
|
|
295
|
+
const stored = localStorage.getItem(this._storageKey)
|
|
296
|
+
if (stored) {
|
|
297
|
+
const { token, user, originalUser } = JSON.parse(stored) as StoredSessionData<TUser>
|
|
298
|
+
this._token = token
|
|
299
|
+
this._user = user
|
|
300
|
+
this._originalUser = originalUser ?? null
|
|
301
|
+
}
|
|
302
|
+
} catch {
|
|
303
|
+
// Invalid stored data, ignore
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Persist session to localStorage
|
|
309
|
+
* Call after login() to save session
|
|
310
|
+
* @protected
|
|
311
|
+
*/
|
|
312
|
+
protected persist(): void {
|
|
313
|
+
if (this._token && this._user) {
|
|
314
|
+
const data: StoredSessionData<TUser> = {
|
|
315
|
+
token: this._token,
|
|
316
|
+
user: this._user
|
|
317
|
+
}
|
|
318
|
+
if (this._originalUser) {
|
|
319
|
+
data.originalUser = this._originalUser
|
|
320
|
+
}
|
|
321
|
+
localStorage.setItem(this._storageKey, JSON.stringify(data))
|
|
322
|
+
} else {
|
|
323
|
+
localStorage.removeItem(this._storageKey)
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Arrow functions to preserve `this` when used as callbacks
|
|
328
|
+
logout = (): void => {
|
|
329
|
+
this._originalUser = null
|
|
330
|
+
this.clearSession()
|
|
331
|
+
localStorage.removeItem(this._storageKey)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
isAuthenticated = (): boolean => {
|
|
335
|
+
return !!this._token
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
getToken = (): string | null => {
|
|
339
|
+
return this._token
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
getUser = (): TUser | null => {
|
|
343
|
+
return this._user
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ─────────────────────────────────────────────────────────────────
|
|
347
|
+
// Impersonation support
|
|
348
|
+
// ─────────────────────────────────────────────────────────────────
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Check if currently impersonating another user
|
|
352
|
+
*/
|
|
353
|
+
isImpersonating = (): boolean => {
|
|
354
|
+
return this._originalUser !== null
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Get the original user (admin) when impersonating
|
|
359
|
+
* @returns Original user or null if not impersonating
|
|
360
|
+
*/
|
|
361
|
+
getOriginalUser = (): TUser | null => {
|
|
362
|
+
return this._originalUser
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Start impersonating another user
|
|
367
|
+
*
|
|
368
|
+
* @param targetUser - User to impersonate
|
|
369
|
+
* @returns The impersonated user
|
|
370
|
+
*/
|
|
371
|
+
impersonate = (targetUser: TUser): TUser => {
|
|
372
|
+
if (!this._user) {
|
|
373
|
+
throw new Error('Must be authenticated to impersonate')
|
|
374
|
+
}
|
|
375
|
+
if (this._originalUser) {
|
|
376
|
+
throw new Error('Already impersonating. Stop first.')
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
this._originalUser = this._user
|
|
380
|
+
this._user = targetUser
|
|
381
|
+
this.persist()
|
|
382
|
+
|
|
383
|
+
return targetUser
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Stop impersonating and return to original user
|
|
388
|
+
*
|
|
389
|
+
* @returns The original user
|
|
390
|
+
*/
|
|
391
|
+
stopImpersonating = (): TUser => {
|
|
392
|
+
if (!this._originalUser) {
|
|
393
|
+
throw new Error('Not currently impersonating')
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const original = this._originalUser
|
|
397
|
+
this._user = this._originalUser
|
|
398
|
+
this._originalUser = null
|
|
399
|
+
this.persist()
|
|
400
|
+
|
|
401
|
+
return original
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Destroy session - clears token and localStorage
|
|
406
|
+
* @override
|
|
407
|
+
*/
|
|
408
|
+
destroySession = (): void => {
|
|
409
|
+
this._token = null
|
|
410
|
+
localStorage.removeItem(this._storageKey)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// ─────────────────────────────────────────────────────────────────
|
|
414
|
+
// Signal integration
|
|
415
|
+
// ─────────────────────────────────────────────────────────────────
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Connect authAdapter to signals for reactive impersonation
|
|
419
|
+
*
|
|
420
|
+
* Call this during kernel boot to enable signal-driven impersonation.
|
|
421
|
+
* The adapter will listen to auth:impersonate and auth:impersonate:stop
|
|
422
|
+
* signals and update its internal state automatically.
|
|
423
|
+
*
|
|
424
|
+
* @param signals - Signals instance (from orchestrator.signals)
|
|
425
|
+
* @returns Cleanup function to unsubscribe
|
|
426
|
+
*/
|
|
427
|
+
connectSignals(signals: SignalBus | null): () => void {
|
|
428
|
+
if (!signals) return () => {}
|
|
429
|
+
|
|
430
|
+
const cleanups: Array<() => void> = []
|
|
431
|
+
|
|
432
|
+
// Listen for impersonate signal
|
|
433
|
+
cleanups.push(signals.on('auth:impersonate', (event) => {
|
|
434
|
+
const data = event.data as { target?: TUser } | undefined
|
|
435
|
+
const targetUser = data?.target
|
|
436
|
+
if (targetUser && !this._originalUser) {
|
|
437
|
+
this._originalUser = this._user
|
|
438
|
+
this._user = targetUser
|
|
439
|
+
this.persist()
|
|
440
|
+
}
|
|
441
|
+
}))
|
|
442
|
+
|
|
443
|
+
// Listen for stop impersonation signal
|
|
444
|
+
cleanups.push(signals.on('auth:impersonate:stop', () => {
|
|
445
|
+
if (this._originalUser) {
|
|
446
|
+
this._user = this._originalUser
|
|
447
|
+
this._originalUser = null
|
|
448
|
+
this.persist()
|
|
449
|
+
}
|
|
450
|
+
}))
|
|
451
|
+
|
|
452
|
+
// Return cleanup function
|
|
453
|
+
return () => {
|
|
454
|
+
cleanups.forEach(cleanup => cleanup?.())
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export default SessionAuthAdapter
|