@acorex/platform 0.0.0-ACOREX
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/auth/README.md +3 -0
- package/common/README.md +3 -0
- package/core/README.md +4 -0
- package/fesm2022/acorex-platform-auth.mjs +1362 -0
- package/fesm2022/acorex-platform-auth.mjs.map +1 -0
- package/fesm2022/acorex-platform-common-common-settings.provider-G9XcXXOG.mjs +127 -0
- package/fesm2022/acorex-platform-common-common-settings.provider-G9XcXXOG.mjs.map +1 -0
- package/fesm2022/acorex-platform-common.mjs +4601 -0
- package/fesm2022/acorex-platform-common.mjs.map +1 -0
- package/fesm2022/acorex-platform-core.mjs +4374 -0
- package/fesm2022/acorex-platform-core.mjs.map +1 -0
- package/fesm2022/acorex-platform-domain.mjs +3234 -0
- package/fesm2022/acorex-platform-domain.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-builder.mjs +2847 -0
- package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-components-binding-expression-editor-popup.component-CXEdvDTf.mjs +121 -0
- package/fesm2022/acorex-platform-layout-components-binding-expression-editor-popup.component-CXEdvDTf.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-components.mjs +8583 -0
- package/fesm2022/acorex-platform-layout-components.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-designer.mjs +2474 -0
- package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-entity.mjs +19150 -0
- package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-views.mjs +1468 -0
- package/fesm2022/acorex-platform-layout-views.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-widget-core.mjs +2950 -0
- package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-widgets-button-widget-designer.component-Dy7jF-oD.mjs +72 -0
- package/fesm2022/acorex-platform-layout-widgets-button-widget-designer.component-Dy7jF-oD.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-widgets-file-list-popup.component-9uCkMxcc.mjs +158 -0
- package/fesm2022/acorex-platform-layout-widgets-file-list-popup.component-9uCkMxcc.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-widgets-image-preview.popup-C_EPAvCU.mjs +29 -0
- package/fesm2022/acorex-platform-layout-widgets-image-preview.popup-C_EPAvCU.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-widgets-page-widget-designer.component-D10yO28c.mjs +172 -0
- package/fesm2022/acorex-platform-layout-widgets-page-widget-designer.component-D10yO28c.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-BGQqY5Mw.mjs +111 -0
- package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-BGQqY5Mw.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-DmzNTYiS.mjs +274 -0
- package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-DmzNTYiS.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-widgets-tabular-data-view-popup.component-BNG_588B.mjs +64 -0
- package/fesm2022/acorex-platform-layout-widgets-tabular-data-view-popup.component-BNG_588B.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-widgets-text-block-widget-designer.component-Vo4fWHtX.mjs +34 -0
- package/fesm2022/acorex-platform-layout-widgets-text-block-widget-designer.component-Vo4fWHtX.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-widgets.mjs +29791 -0
- package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -0
- package/fesm2022/acorex-platform-native.mjs +155 -0
- package/fesm2022/acorex-platform-native.mjs.map +1 -0
- package/fesm2022/acorex-platform-runtime-catalog-command-definition.mjs +20 -0
- package/fesm2022/acorex-platform-runtime-catalog-command-definition.mjs.map +1 -0
- package/fesm2022/acorex-platform-runtime-catalog-query-definition.mjs +20 -0
- package/fesm2022/acorex-platform-runtime-catalog-query-definition.mjs.map +1 -0
- package/fesm2022/acorex-platform-runtime.mjs +899 -0
- package/fesm2022/acorex-platform-runtime.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-Cvvr4HnL.mjs +160 -0
- package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-Cvvr4HnL.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-TYoLN1Jq.mjs +120 -0
- package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-TYoLN1Jq.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-C2z5Lq9y.mjs +237 -0
- package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-C2z5Lq9y.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default-error-401.component-C7EYJzSr.mjs +31 -0
- package/fesm2022/acorex-platform-themes-default-error-401.component-C7EYJzSr.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default-error-404.component-7MVLMwIa.mjs +25 -0
- package/fesm2022/acorex-platform-themes-default-error-404.component-7MVLMwIa.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default-error-offline.component-DR6G8gPC.mjs +19 -0
- package/fesm2022/acorex-platform-themes-default-error-offline.component-DR6G8gPC.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default.mjs +2589 -0
- package/fesm2022/acorex-platform-themes-default.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-shared-icon-chooser-column.component-CqkWJYdv.mjs +55 -0
- package/fesm2022/acorex-platform-themes-shared-icon-chooser-column.component-CqkWJYdv.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-shared-icon-chooser-view.component-BOTuLdWN.mjs +57 -0
- package/fesm2022/acorex-platform-themes-shared-icon-chooser-view.component-BOTuLdWN.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-shared-settings.provider-DSs1o1M6.mjs +168 -0
- package/fesm2022/acorex-platform-themes-shared-settings.provider-DSs1o1M6.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-CHfrTtol.mjs +65 -0
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-CHfrTtol.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-view.component-BSmvnUVq.mjs +64 -0
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-view.component-BSmvnUVq.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-shared.mjs +2125 -0
- package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -0
- package/fesm2022/acorex-platform-workflow.mjs +2501 -0
- package/fesm2022/acorex-platform-workflow.mjs.map +1 -0
- package/fesm2022/acorex-platform.mjs +6 -0
- package/fesm2022/acorex-platform.mjs.map +1 -0
- package/layout/builder/README.md +1578 -0
- package/layout/components/README.md +3 -0
- package/layout/designer/README.md +4 -0
- package/layout/entity/README.md +4 -0
- package/layout/views/README.md +3 -0
- package/layout/widget-core/README.md +4 -0
- package/layout/widgets/README.md +3 -0
- package/native/README.md +4 -0
- package/package.json +103 -0
- package/runtime/README.md +3 -0
- package/themes/default/README.md +3 -0
- package/themes/shared/README.md +3 -0
- package/types/acorex-platform-auth.d.ts +680 -0
- package/types/acorex-platform-common.d.ts +2926 -0
- package/types/acorex-platform-core.d.ts +2896 -0
- package/types/acorex-platform-domain.d.ts +2353 -0
- package/types/acorex-platform-layout-builder.d.ts +926 -0
- package/types/acorex-platform-layout-components.d.ts +2903 -0
- package/types/acorex-platform-layout-designer.d.ts +422 -0
- package/types/acorex-platform-layout-entity.d.ts +3189 -0
- package/types/acorex-platform-layout-views.d.ts +667 -0
- package/types/acorex-platform-layout-widget-core.d.ts +1086 -0
- package/types/acorex-platform-layout-widgets.d.ts +5478 -0
- package/types/acorex-platform-native.d.ts +28 -0
- package/types/acorex-platform-runtime-catalog-command-definition.d.ts +137 -0
- package/types/acorex-platform-runtime-catalog-query-definition.d.ts +125 -0
- package/types/acorex-platform-runtime.d.ts +470 -0
- package/types/acorex-platform-themes-default.d.ts +573 -0
- package/types/acorex-platform-themes-shared.d.ts +170 -0
- package/types/acorex-platform-workflow.d.ts +1806 -0
- package/types/acorex-platform.d.ts +2 -0
- package/workflow/README.md +4 -0
|
@@ -0,0 +1,1362 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, Injector, Injectable, signal, inject, Input, Directive, provideAppInitializer, Optional, Inject, NgModule, input, output, Component } from '@angular/core';
|
|
3
|
+
import { AXPBroadcastEventService, AXP_SESSION_SERVICE } from '@acorex/platform/core';
|
|
4
|
+
import { isEmpty } from 'lodash-es';
|
|
5
|
+
import { map, BehaviorSubject, shareReplay, defaultIfEmpty, switchMap, filter, from, first } from 'rxjs';
|
|
6
|
+
import { AXDataSource } from '@acorex/cdk/common';
|
|
7
|
+
import { AXPWidgetsCatalog } from '@acorex/platform/layout/widget-core';
|
|
8
|
+
|
|
9
|
+
const AXP_APPLICATION_LOADER = new InjectionToken('AXP_APPLICATION_LOADER', {
|
|
10
|
+
providedIn: 'root',
|
|
11
|
+
factory: () => {
|
|
12
|
+
return new AXPApplicationDefaultLoader();
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
class AXPApplicationDefaultLoader {
|
|
16
|
+
async getList(context) {
|
|
17
|
+
return [
|
|
18
|
+
{
|
|
19
|
+
id: '1',
|
|
20
|
+
name: 'default-app',
|
|
21
|
+
title: 'Default Application',
|
|
22
|
+
version: '1.0.0',
|
|
23
|
+
edition: {
|
|
24
|
+
id: 'default-edition-1',
|
|
25
|
+
title: 'Standard',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: '2',
|
|
30
|
+
name: 'default-app',
|
|
31
|
+
title: 'Default Application',
|
|
32
|
+
version: '1.0.0',
|
|
33
|
+
edition: {
|
|
34
|
+
id: 'default-edition-2',
|
|
35
|
+
title: 'Standard',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const AXP_TENANT_LOADER = new InjectionToken('AXP_TENANT_LOADER', {
|
|
43
|
+
providedIn: 'root',
|
|
44
|
+
factory: () => {
|
|
45
|
+
return new AXPTenantDefaultLoader();
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
class AXPTenantDefaultLoader {
|
|
49
|
+
async getList(context) {
|
|
50
|
+
return [
|
|
51
|
+
{
|
|
52
|
+
id: '1',
|
|
53
|
+
name: 'default-tenant',
|
|
54
|
+
title: 'Default Tenant',
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
class AXPAuthStrategyRegistryService {
|
|
61
|
+
constructor(injector) {
|
|
62
|
+
this.strategies = new Map();
|
|
63
|
+
this.injector = injector;
|
|
64
|
+
}
|
|
65
|
+
register(...plugins) {
|
|
66
|
+
plugins.forEach(t => {
|
|
67
|
+
const childInjector = Injector.create({ providers: [{ provide: t, useClass: t, deps: [] }], parent: this.injector });
|
|
68
|
+
const strategy = childInjector.get(t);
|
|
69
|
+
if (strategy) {
|
|
70
|
+
this.strategies.set(strategy.name, strategy);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
get(strategyKey) {
|
|
75
|
+
return this.strategies.get(strategyKey);
|
|
76
|
+
}
|
|
77
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPAuthStrategyRegistryService, deps: [{ token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
78
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPAuthStrategyRegistryService, providedIn: 'root' }); }
|
|
79
|
+
}
|
|
80
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPAuthStrategyRegistryService, decorators: [{
|
|
81
|
+
type: Injectable,
|
|
82
|
+
args: [{
|
|
83
|
+
providedIn: 'root'
|
|
84
|
+
}]
|
|
85
|
+
}], ctorParameters: () => [{ type: i0.Injector }] });
|
|
86
|
+
|
|
87
|
+
const AXP_FEATURE_LOADER = new InjectionToken('AXP_FEATURE_LOADER', {
|
|
88
|
+
providedIn: 'root',
|
|
89
|
+
factory: () => {
|
|
90
|
+
return new AXPFeatureDefaultLoader();
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
class AXPFeatureDefaultLoader {
|
|
94
|
+
async getList(context) {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
class AXPFeatureDirective {
|
|
100
|
+
constructor(templateRef, viewContainer) {
|
|
101
|
+
this.templateRef = templateRef;
|
|
102
|
+
this.viewContainer = viewContainer;
|
|
103
|
+
this.hasView = signal(false, ...(ngDevMode ? [{ debugName: "hasView" }] : /* istanbul ignore next */ []));
|
|
104
|
+
this.sessionService = inject(AXPSessionService);
|
|
105
|
+
}
|
|
106
|
+
set feature(featureKeys) {
|
|
107
|
+
const keys = !featureKeys ? [] : (Array.isArray(featureKeys) ? featureKeys : [featureKeys]);
|
|
108
|
+
if (keys.length == 0) {
|
|
109
|
+
// If featureKey is null or empty, decide the default behavior here
|
|
110
|
+
this.viewContainer.createEmbeddedView(this.templateRef);
|
|
111
|
+
this.hasView.set(true);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
this.subscription = this.sessionService.features$
|
|
115
|
+
//.pipe(first())
|
|
116
|
+
.subscribe(() => {
|
|
117
|
+
if (this.sessionService.isFeatureEnabled(...keys)) {
|
|
118
|
+
if (!this.hasView()) {
|
|
119
|
+
this.viewContainer.createEmbeddedView(this.templateRef);
|
|
120
|
+
this.hasView.set(true);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
this.viewContainer.clear();
|
|
125
|
+
this.hasView.set(false);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
set featureElse(elseTemplateRef) {
|
|
130
|
+
if (!this.hasView()) {
|
|
131
|
+
this.viewContainer.createEmbeddedView(elseTemplateRef);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
ngOnDestroy() {
|
|
135
|
+
this.subscription?.unsubscribe();
|
|
136
|
+
}
|
|
137
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFeatureDirective, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
138
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: AXPFeatureDirective, isStandalone: false, selector: "[feature]", inputs: { feature: "feature", featureElse: "featureElse" }, ngImport: i0 }); }
|
|
139
|
+
}
|
|
140
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFeatureDirective, decorators: [{
|
|
141
|
+
type: Directive,
|
|
142
|
+
args: [{
|
|
143
|
+
selector: '[feature]',
|
|
144
|
+
standalone: false
|
|
145
|
+
}]
|
|
146
|
+
}], ctorParameters: () => [{ type: i0.TemplateRef }, { type: i0.ViewContainerRef }], propDecorators: { feature: [{
|
|
147
|
+
type: Input
|
|
148
|
+
}], featureElse: [{
|
|
149
|
+
type: Input
|
|
150
|
+
}] } });
|
|
151
|
+
|
|
152
|
+
class AXPUnauthorizedError extends Error {
|
|
153
|
+
constructor(message, data) {
|
|
154
|
+
super(message);
|
|
155
|
+
this.data = data;
|
|
156
|
+
this.name = 'AXPUnauthorizedError';
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
class AXPUnauthenticatedError extends Error {
|
|
160
|
+
constructor(message, data) {
|
|
161
|
+
super(message);
|
|
162
|
+
this.data = data;
|
|
163
|
+
this.name = 'AXPUnauthenticatedError';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const AXPFeatureGuard = (route, state) => {
|
|
168
|
+
const sessionService = inject(AXPSessionService);
|
|
169
|
+
const requiredFeatures = route.data['requiredFeature'];
|
|
170
|
+
return sessionService.features$.pipe(map(() => {
|
|
171
|
+
const keys = !requiredFeatures ? [] : (Array.isArray(requiredFeatures) ? requiredFeatures : [requiredFeatures]);
|
|
172
|
+
const hasFeature = keys.length == 0 || sessionService.isFeatureEnabled(...keys);
|
|
173
|
+
if (!hasFeature) {
|
|
174
|
+
throw new AXPUnauthorizedError(`Access Denied. You do not have access to this feature. Required feature(s): ${keys.join(', ')}. Please contact support if you need access.`, {
|
|
175
|
+
redirectUrl: state.url
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return true;
|
|
179
|
+
}));
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Optional injection token for feature checker.
|
|
184
|
+
* If provided, the checker will be called to potentially override
|
|
185
|
+
* feature enablement results based on custom logic.
|
|
186
|
+
*/
|
|
187
|
+
const AXP_FEATURE_CHECKER = new InjectionToken('AXP_FEATURE_CHECKER');
|
|
188
|
+
|
|
189
|
+
const AXP_PERMISSION_LOADER = new InjectionToken('AXP_PERMISSION_LOADER', {
|
|
190
|
+
providedIn: 'root',
|
|
191
|
+
factory: () => {
|
|
192
|
+
return new AXPPermissionDefaultLoader();
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
class AXPPermissionDefaultLoader {
|
|
196
|
+
async getList(context) {
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Optional injection token for permission checker.
|
|
203
|
+
* If provided, the checker will be called to potentially override
|
|
204
|
+
* authorization results based on custom logic.
|
|
205
|
+
*/
|
|
206
|
+
const AXP_PERMISSION_CHECKER = new InjectionToken('AXP_PERMISSION_CHECKER');
|
|
207
|
+
|
|
208
|
+
class AXPSessionContext {
|
|
209
|
+
get user() {
|
|
210
|
+
return this._user;
|
|
211
|
+
}
|
|
212
|
+
get tenant() {
|
|
213
|
+
return this._tenant;
|
|
214
|
+
}
|
|
215
|
+
get application() {
|
|
216
|
+
return this._application;
|
|
217
|
+
}
|
|
218
|
+
constructor(context) {
|
|
219
|
+
this._user = null;
|
|
220
|
+
this._tenant = null;
|
|
221
|
+
this._application = null;
|
|
222
|
+
this._user = context.user;
|
|
223
|
+
this._tenant = context.tenant;
|
|
224
|
+
this._application = context.application;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
var AXPSessionStatus;
|
|
228
|
+
(function (AXPSessionStatus) {
|
|
229
|
+
AXPSessionStatus["Authenticated"] = "authenticated";
|
|
230
|
+
AXPSessionStatus["Unauthenticated"] = "unauthenticated";
|
|
231
|
+
AXPSessionStatus["Unauthorized"] = "unauthorized";
|
|
232
|
+
AXPSessionStatus["Authorized"] = "authorized";
|
|
233
|
+
AXPSessionStatus["Expired"] = "expired";
|
|
234
|
+
AXPSessionStatus["SignedOut"] = "signedOut";
|
|
235
|
+
})(AXPSessionStatus || (AXPSessionStatus = {}));
|
|
236
|
+
|
|
237
|
+
class AXPSessionService {
|
|
238
|
+
constructor() {
|
|
239
|
+
this.eventService = inject(AXPBroadcastEventService);
|
|
240
|
+
this.authStrategyRegistry = inject(AXPAuthStrategyRegistryService);
|
|
241
|
+
this.injector = inject(Injector);
|
|
242
|
+
this.permissionLoader = inject(AXP_PERMISSION_LOADER);
|
|
243
|
+
this.featureLoader = inject(AXP_FEATURE_LOADER);
|
|
244
|
+
this.tenantLoader = inject(AXP_TENANT_LOADER);
|
|
245
|
+
this.applicationLoader = inject(AXP_APPLICATION_LOADER);
|
|
246
|
+
this.permissionChecker = inject(AXP_PERMISSION_CHECKER, { optional: true });
|
|
247
|
+
this.featureChecker = inject(AXP_FEATURE_CHECKER, { optional: true });
|
|
248
|
+
this.status = new BehaviorSubject(AXPSessionStatus.Unauthenticated);
|
|
249
|
+
this.status$ = this.status.asObservable().pipe(shareReplay(1));
|
|
250
|
+
// Add loading state to prevent premature redirects
|
|
251
|
+
this.isLoading = new BehaviorSubject(true);
|
|
252
|
+
this.isLoading$ = this.isLoading.asObservable().pipe(shareReplay(1));
|
|
253
|
+
this.currentUserSubject = new BehaviorSubject(null);
|
|
254
|
+
this.user$ = this.currentUserSubject.asObservable().pipe(shareReplay(1));
|
|
255
|
+
this.currentTenantSubject = new BehaviorSubject(null);
|
|
256
|
+
this.tenant$ = this.currentTenantSubject.asObservable().pipe(shareReplay(1));
|
|
257
|
+
this.currentApplicationSubject = new BehaviorSubject(null);
|
|
258
|
+
this.application$ = this.currentApplicationSubject.asObservable().pipe(shareReplay(1));
|
|
259
|
+
this.permissionsSubject = new BehaviorSubject([]);
|
|
260
|
+
this.permissions$ = this.permissionsSubject.asObservable().pipe(shareReplay(1), defaultIfEmpty([]));
|
|
261
|
+
this.featuresSubject = new BehaviorSubject([]);
|
|
262
|
+
this.features$ = this.featuresSubject.asObservable().pipe(shareReplay(1), defaultIfEmpty([]));
|
|
263
|
+
this.isAuthenticated$ = this.status$.pipe(map((status) => {
|
|
264
|
+
const isAuth = status === AXPSessionStatus.Authenticated || status === AXPSessionStatus.Authorized;
|
|
265
|
+
return isAuth;
|
|
266
|
+
}), shareReplay(1));
|
|
267
|
+
// Add a new observable that considers loading state
|
|
268
|
+
this.isAuthenticatedWithLoading$ = this.isLoading$.pipe(switchMap((loading) => {
|
|
269
|
+
if (loading) {
|
|
270
|
+
// Wait for loading to complete, then return authentication status
|
|
271
|
+
return this.isLoading$.pipe(filter((isLoading) => !isLoading), switchMap(() => this.isAuthenticated$));
|
|
272
|
+
}
|
|
273
|
+
// If not loading, return current authentication status
|
|
274
|
+
return this.isAuthenticated$;
|
|
275
|
+
}), shareReplay(1));
|
|
276
|
+
this.isAuthorized$ = this.status$.pipe(map((status) => status === AXPSessionStatus.Authorized), shareReplay(1));
|
|
277
|
+
}
|
|
278
|
+
static { this.SESSION_KEY = 'AXP_SESSION'; }
|
|
279
|
+
get user() {
|
|
280
|
+
const session = this.getSessionData();
|
|
281
|
+
if (session?.user && !this.currentUserSubject.value) {
|
|
282
|
+
this.currentUserSubject.next(session.user);
|
|
283
|
+
}
|
|
284
|
+
return this.currentUserSubject.value;
|
|
285
|
+
}
|
|
286
|
+
get tenant() {
|
|
287
|
+
const session = this.getSessionData();
|
|
288
|
+
if (session?.tenant && !this.currentTenantSubject.value) {
|
|
289
|
+
this.currentTenantSubject.next(session.tenant);
|
|
290
|
+
}
|
|
291
|
+
return this.currentTenantSubject.value;
|
|
292
|
+
}
|
|
293
|
+
get tenants$() {
|
|
294
|
+
return from(this.tenantLoader.getList(this.getContext()));
|
|
295
|
+
}
|
|
296
|
+
get application() {
|
|
297
|
+
const session = this.getSessionData();
|
|
298
|
+
if (session?.application && !this.currentApplicationSubject.value) {
|
|
299
|
+
this.currentApplicationSubject.next(session.application);
|
|
300
|
+
}
|
|
301
|
+
return this.currentApplicationSubject.value;
|
|
302
|
+
}
|
|
303
|
+
get applications$() {
|
|
304
|
+
return from(this.applicationLoader.getList(this.getContext()));
|
|
305
|
+
}
|
|
306
|
+
get permissions() {
|
|
307
|
+
return this.permissionsSubject.value ?? [];
|
|
308
|
+
}
|
|
309
|
+
get features() {
|
|
310
|
+
return this.featuresSubject.value ?? [];
|
|
311
|
+
}
|
|
312
|
+
async restoreSession() {
|
|
313
|
+
this.isLoading.next(true);
|
|
314
|
+
try {
|
|
315
|
+
const sessionData = this.getSessionData();
|
|
316
|
+
if (sessionData) {
|
|
317
|
+
if (sessionData.user) {
|
|
318
|
+
this.currentUserSubject.next(sessionData.user);
|
|
319
|
+
// Restore tenant and application from session data
|
|
320
|
+
if (sessionData.tenant) {
|
|
321
|
+
this.currentTenantSubject.next(sessionData.tenant);
|
|
322
|
+
}
|
|
323
|
+
if (sessionData.application) {
|
|
324
|
+
this.currentApplicationSubject.next(sessionData.application);
|
|
325
|
+
}
|
|
326
|
+
this.status.next(AXPSessionStatus.Authenticated);
|
|
327
|
+
await this.loadPermissions();
|
|
328
|
+
await this.loadFeatures();
|
|
329
|
+
await this.signInComplete();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
this.status.next(AXPSessionStatus.Unauthorized);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
this.status.next(AXPSessionStatus.Unauthorized);
|
|
338
|
+
}
|
|
339
|
+
finally {
|
|
340
|
+
this.isLoading.next(false);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
async signin(credentials) {
|
|
344
|
+
this.isLoading.next(true);
|
|
345
|
+
try {
|
|
346
|
+
//
|
|
347
|
+
// this.clearSession();
|
|
348
|
+
//
|
|
349
|
+
const strategy = this.authStrategyRegistry.get(credentials.strategy);
|
|
350
|
+
if (!strategy) {
|
|
351
|
+
throw new Error(`Authentication strategy '${credentials.strategy}' is not supported`);
|
|
352
|
+
}
|
|
353
|
+
const result = await strategy.signin(credentials);
|
|
354
|
+
if (!result)
|
|
355
|
+
return;
|
|
356
|
+
if (result?.succeed) {
|
|
357
|
+
this.currentUserSubject.next(result.data.user);
|
|
358
|
+
this.setSession({
|
|
359
|
+
accessToken: result.data.accessToken,
|
|
360
|
+
refreshToken: result.data.refreshToken,
|
|
361
|
+
strategy: credentials.strategy,
|
|
362
|
+
user: result.data.user,
|
|
363
|
+
application: result.data?.application,
|
|
364
|
+
tenant: result.data?.tenant,
|
|
365
|
+
expiresIn: result.data?.expiresIn,
|
|
366
|
+
idToken: result.data?.idToken ?? null,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
this.status.next(AXPSessionStatus.Unauthenticated);
|
|
371
|
+
throw new Error(`Invalid Username or Password`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
catch (error) {
|
|
375
|
+
console.error('Signin error:', error);
|
|
376
|
+
this.status.next(AXPSessionStatus.Unauthenticated);
|
|
377
|
+
throw error;
|
|
378
|
+
}
|
|
379
|
+
finally {
|
|
380
|
+
this.isLoading.next(false);
|
|
381
|
+
console.log('Signin process completed');
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
async updateToken(params) {
|
|
385
|
+
this.isLoading.next(true);
|
|
386
|
+
try {
|
|
387
|
+
const strategyName = this.getSessionData()?.strategy;
|
|
388
|
+
if (!strategyName) {
|
|
389
|
+
throw new Error('Strategy not found');
|
|
390
|
+
}
|
|
391
|
+
const strategy = this.authStrategyRegistry.get(strategyName);
|
|
392
|
+
if (!strategy) {
|
|
393
|
+
throw new Error(`Authentication strategy '${this.getSessionData()?.strategy}' is not supported`);
|
|
394
|
+
}
|
|
395
|
+
const result = await strategy.updateToken(params);
|
|
396
|
+
if (result?.succeed) {
|
|
397
|
+
this.currentUserSubject.next(result.data.user);
|
|
398
|
+
this.setSession({
|
|
399
|
+
accessToken: result.data.accessToken,
|
|
400
|
+
refreshToken: result.data.refreshToken,
|
|
401
|
+
strategy: strategyName,
|
|
402
|
+
user: result.data.user,
|
|
403
|
+
application: result.data?.application,
|
|
404
|
+
tenant: result.data?.tenant,
|
|
405
|
+
expiresIn: result.data?.expiresIn,
|
|
406
|
+
idToken: result.data?.idToken ?? null,
|
|
407
|
+
});
|
|
408
|
+
this.status.next(AXPSessionStatus.Authenticated);
|
|
409
|
+
// If we have both tenant and application, complete the sign-in
|
|
410
|
+
if (this.application && this.tenant) {
|
|
411
|
+
await this.restoreSession();
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
this.status.next(AXPSessionStatus.Unauthenticated);
|
|
416
|
+
throw new Error(`Invalid Username or Password`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
catch (error) {
|
|
420
|
+
console.error('Update token error:', error);
|
|
421
|
+
throw error;
|
|
422
|
+
}
|
|
423
|
+
finally {
|
|
424
|
+
this.isLoading.next(false);
|
|
425
|
+
console.log('Update token process completed');
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
async signout() {
|
|
429
|
+
console.log('Signing out...');
|
|
430
|
+
this.isLoading.next(true);
|
|
431
|
+
try {
|
|
432
|
+
const sessionData = this.getSessionData();
|
|
433
|
+
if (sessionData?.strategy) {
|
|
434
|
+
const strategy = this.authStrategyRegistry.get(sessionData?.strategy);
|
|
435
|
+
if (strategy) {
|
|
436
|
+
try {
|
|
437
|
+
await strategy.signout();
|
|
438
|
+
}
|
|
439
|
+
catch (error) { }
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
//
|
|
443
|
+
const userId = this.user?.id;
|
|
444
|
+
this.clearSession();
|
|
445
|
+
this.eventService.publish(AXPSessionStatus.SignedOut, { id: userId });
|
|
446
|
+
this.isLoading.next(false);
|
|
447
|
+
this.status.next(AXPSessionStatus.SignedOut);
|
|
448
|
+
}
|
|
449
|
+
finally {
|
|
450
|
+
this.isLoading.next(false);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
async refreshToken() {
|
|
454
|
+
console.log('Refreshing token...');
|
|
455
|
+
return new Promise(async (resolve, reject) => {
|
|
456
|
+
const sessionData = this.getSessionData();
|
|
457
|
+
if (!sessionData || !sessionData?.refreshToken) {
|
|
458
|
+
console.log('No session data or refresh token found');
|
|
459
|
+
reject(new Error('No refresh token available'));
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
const strategy = this.authStrategyRegistry.get(sessionData.strategy);
|
|
463
|
+
if (!strategy) {
|
|
464
|
+
console.error('Authentication strategy not found:', sessionData.strategy);
|
|
465
|
+
reject(new Error(`Authentication strategy '${sessionData.strategy}' is not found`));
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
try {
|
|
469
|
+
const result = await strategy.refreshToken(this.getContext());
|
|
470
|
+
if (result.succeed) {
|
|
471
|
+
console.log('Token refresh successful');
|
|
472
|
+
this.setSession(result.data);
|
|
473
|
+
resolve(result.data?.accessToken);
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
console.error('Token refresh failed');
|
|
477
|
+
this.clearSession();
|
|
478
|
+
this.status.next(AXPSessionStatus.Expired);
|
|
479
|
+
reject(new Error('Token refresh failed'));
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
catch (error) {
|
|
483
|
+
console.error('Error during token refresh:', error);
|
|
484
|
+
this.clearSession();
|
|
485
|
+
this.status.next(AXPSessionStatus.Expired);
|
|
486
|
+
reject(error);
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
async loadPermissions() {
|
|
491
|
+
try {
|
|
492
|
+
const permissions = await this.permissionLoader.getList(this.getContext());
|
|
493
|
+
this.permissionsSubject.next(permissions ?? []);
|
|
494
|
+
}
|
|
495
|
+
catch (error) {
|
|
496
|
+
console.error('Error loading permissions:', error);
|
|
497
|
+
this.permissionsSubject.next([]);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
async loadFeatures() {
|
|
501
|
+
try {
|
|
502
|
+
const features = await this.featureLoader.getList(this.getContext());
|
|
503
|
+
this.featuresSubject.next(features ?? []);
|
|
504
|
+
}
|
|
505
|
+
catch (error) {
|
|
506
|
+
console.error('Error loading features:', error);
|
|
507
|
+
this.featuresSubject.next([]);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
async signInComplete() {
|
|
511
|
+
// Ensure we have the required data
|
|
512
|
+
if (!this.user) {
|
|
513
|
+
this.status.next(AXPSessionStatus.Unauthenticated);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
// Set status to Authorized
|
|
517
|
+
this.status.next(AXPSessionStatus.Authorized);
|
|
518
|
+
// Double-check the status was set correctly
|
|
519
|
+
setTimeout(() => {
|
|
520
|
+
console.log('Status after timeout:', this.status.value);
|
|
521
|
+
}, 100);
|
|
522
|
+
}
|
|
523
|
+
setSession(tokens) {
|
|
524
|
+
const sessionData = {
|
|
525
|
+
accessToken: tokens.accessToken,
|
|
526
|
+
refreshToken: tokens.refreshToken,
|
|
527
|
+
strategy: tokens.strategy,
|
|
528
|
+
user: tokens.user,
|
|
529
|
+
application: tokens.application,
|
|
530
|
+
tenant: tokens.tenant,
|
|
531
|
+
expiresIn: tokens.expiresIn,
|
|
532
|
+
idToken: tokens.idToken,
|
|
533
|
+
};
|
|
534
|
+
// Update subjects
|
|
535
|
+
if (tokens.user) {
|
|
536
|
+
this.currentUserSubject.next(tokens.user);
|
|
537
|
+
}
|
|
538
|
+
if (tokens.tenant) {
|
|
539
|
+
this.currentTenantSubject.next(tokens.tenant);
|
|
540
|
+
}
|
|
541
|
+
if (tokens.application) {
|
|
542
|
+
this.currentApplicationSubject.next(tokens.application);
|
|
543
|
+
}
|
|
544
|
+
localStorage.setItem(AXPSessionService.SESSION_KEY, JSON.stringify(sessionData));
|
|
545
|
+
}
|
|
546
|
+
setStrategy(strategy) {
|
|
547
|
+
const sessionData = this.getSessionData();
|
|
548
|
+
const newSessionData = { ...sessionData, strategy };
|
|
549
|
+
this.setSession(newSessionData);
|
|
550
|
+
}
|
|
551
|
+
getSessionData() {
|
|
552
|
+
try {
|
|
553
|
+
const sessionDataString = localStorage.getItem(AXPSessionService.SESSION_KEY);
|
|
554
|
+
if (sessionDataString) {
|
|
555
|
+
const sessionData = JSON.parse(sessionDataString);
|
|
556
|
+
return sessionData;
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
return null;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
catch (error) {
|
|
563
|
+
localStorage.removeItem(AXPSessionService.SESSION_KEY);
|
|
564
|
+
return null;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
clearSession() {
|
|
568
|
+
//
|
|
569
|
+
this.currentUserSubject.next(null);
|
|
570
|
+
//
|
|
571
|
+
this.currentTenantSubject.next(null);
|
|
572
|
+
//
|
|
573
|
+
this.currentApplicationSubject.next(null);
|
|
574
|
+
//
|
|
575
|
+
this.permissionsSubject.next([]);
|
|
576
|
+
//
|
|
577
|
+
this.featuresSubject.next([]);
|
|
578
|
+
//
|
|
579
|
+
localStorage.removeItem(AXPSessionService.SESSION_KEY);
|
|
580
|
+
}
|
|
581
|
+
authorize(...keys) {
|
|
582
|
+
// Calculate base result
|
|
583
|
+
const baseResult = keys.every((k) => {
|
|
584
|
+
if (isEmpty(k))
|
|
585
|
+
return true;
|
|
586
|
+
// Check if user has the permission
|
|
587
|
+
const hasPermission = this.permissions.indexOf(k) > -1;
|
|
588
|
+
if (!hasPermission)
|
|
589
|
+
return false;
|
|
590
|
+
// Check if permission has required features (if permission definition service is available)
|
|
591
|
+
// Note: This is a lightweight check. Full feature validation happens in permission definition service.
|
|
592
|
+
return true;
|
|
593
|
+
});
|
|
594
|
+
// If permission checker is provided, use it to potentially override the result
|
|
595
|
+
if (this.permissionChecker) {
|
|
596
|
+
const context = this.getContext();
|
|
597
|
+
return this.permissionChecker.check(keys, context, baseResult);
|
|
598
|
+
}
|
|
599
|
+
return baseResult;
|
|
600
|
+
}
|
|
601
|
+
isFeatureEnabled(...keys) {
|
|
602
|
+
// Calculate base result
|
|
603
|
+
const baseResult = keys.every((k) => isEmpty(k) || this.features.some((c) => c.name == k && c.value == true));
|
|
604
|
+
// If feature checker is provided, use it to potentially override the result
|
|
605
|
+
if (this.featureChecker) {
|
|
606
|
+
const context = this.getContext();
|
|
607
|
+
return this.featureChecker.check(keys, context, baseResult);
|
|
608
|
+
}
|
|
609
|
+
return baseResult;
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Checks if a module is enabled for the current tenant/application.
|
|
613
|
+
* Module names are stored as features with value: true when enabled.
|
|
614
|
+
* Module names can be provided in PascalCase (e.g., 'SecurityManagement') or kebab-case (e.g., 'security-management').
|
|
615
|
+
*/
|
|
616
|
+
isModuleEnabled(moduleName) {
|
|
617
|
+
if (!moduleName) {
|
|
618
|
+
return false;
|
|
619
|
+
}
|
|
620
|
+
// Normalize module name: convert kebab-case to PascalCase if needed
|
|
621
|
+
const normalizedModuleName = moduleName.includes('-')
|
|
622
|
+
? moduleName.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join('')
|
|
623
|
+
: moduleName;
|
|
624
|
+
// Module names are stored as features with value: true when enabled
|
|
625
|
+
return this.features.some(f => f.name === normalizedModuleName && f.value === true);
|
|
626
|
+
}
|
|
627
|
+
getToken() {
|
|
628
|
+
const sessionData = this.getSessionData();
|
|
629
|
+
return sessionData?.accessToken;
|
|
630
|
+
}
|
|
631
|
+
getContext() {
|
|
632
|
+
return new AXPSessionContext({
|
|
633
|
+
user: this.user,
|
|
634
|
+
tenant: this.tenant,
|
|
635
|
+
application: this.application,
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPSessionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
639
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPSessionService, providedIn: 'root' }); }
|
|
640
|
+
}
|
|
641
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPSessionService, decorators: [{
|
|
642
|
+
type: Injectable,
|
|
643
|
+
args: [{
|
|
644
|
+
providedIn: 'root',
|
|
645
|
+
}]
|
|
646
|
+
}] });
|
|
647
|
+
|
|
648
|
+
class AXPPermissionDirective {
|
|
649
|
+
constructor(templateRef, viewContainer) {
|
|
650
|
+
this.templateRef = templateRef;
|
|
651
|
+
this.viewContainer = viewContainer;
|
|
652
|
+
this.hasView = signal(false, ...(ngDevMode ? [{ debugName: "hasView" }] : /* istanbul ignore next */ []));
|
|
653
|
+
this.sessionService = inject(AXPSessionService);
|
|
654
|
+
}
|
|
655
|
+
set permission(permissionKeys) {
|
|
656
|
+
const keys = !permissionKeys ? [] : (Array.isArray(permissionKeys) ? permissionKeys : [permissionKeys]);
|
|
657
|
+
if (keys.length == 0) {
|
|
658
|
+
// If permissionKey is null or empty, decide the default behavior here
|
|
659
|
+
this.viewContainer.createEmbeddedView(this.templateRef);
|
|
660
|
+
this.hasView.set(true);
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
this.subscription = this.sessionService.isAuthorized$
|
|
664
|
+
.subscribe((isAuthorized) => {
|
|
665
|
+
if (isAuthorized && this.sessionService.authorize(...keys)) {
|
|
666
|
+
if (!this.hasView()) {
|
|
667
|
+
this.viewContainer.createEmbeddedView(this.templateRef);
|
|
668
|
+
this.hasView.set(true);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
this.viewContainer.clear();
|
|
673
|
+
this.hasView.set(false);
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
set permissionElse(elseTemplateRef) {
|
|
678
|
+
if (!this.hasView()) {
|
|
679
|
+
this.viewContainer.createEmbeddedView(elseTemplateRef);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
ngOnDestroy() {
|
|
683
|
+
this.subscription?.unsubscribe();
|
|
684
|
+
}
|
|
685
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPPermissionDirective, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
686
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: AXPPermissionDirective, isStandalone: false, selector: "[permission]", inputs: { permission: "permission", permissionElse: "permissionElse" }, ngImport: i0 }); }
|
|
687
|
+
}
|
|
688
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPPermissionDirective, decorators: [{
|
|
689
|
+
type: Directive,
|
|
690
|
+
args: [{
|
|
691
|
+
selector: '[permission]',
|
|
692
|
+
standalone: false
|
|
693
|
+
}]
|
|
694
|
+
}], ctorParameters: () => [{ type: i0.TemplateRef }, { type: i0.ViewContainerRef }], propDecorators: { permission: [{
|
|
695
|
+
type: Input
|
|
696
|
+
}], permissionElse: [{
|
|
697
|
+
type: Input
|
|
698
|
+
}] } });
|
|
699
|
+
|
|
700
|
+
const AXPPermissionGuard = (route, state) => {
|
|
701
|
+
const sessionService = inject(AXPSessionService);
|
|
702
|
+
const permissionKeys = route.data['requiredPermission'];
|
|
703
|
+
return sessionService.isAuthorized$.pipe(first(), map((value) => {
|
|
704
|
+
const keys = !permissionKeys ? [] : Array.isArray(permissionKeys) ? permissionKeys : [permissionKeys];
|
|
705
|
+
const hasPermission = keys.length == 0 || sessionService.authorize(...keys);
|
|
706
|
+
if (!hasPermission) {
|
|
707
|
+
throw new AXPUnauthorizedError(`Access denied. Required permissions: ${keys.join(', ')}. Please contact your administrator if you believe this is an error.`, {
|
|
708
|
+
redirectUrl: state.url
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
return true;
|
|
712
|
+
}));
|
|
713
|
+
};
|
|
714
|
+
|
|
715
|
+
class AXPPermissionDefinitionProviderContext {
|
|
716
|
+
constructor() {
|
|
717
|
+
this.builders = new Map();
|
|
718
|
+
}
|
|
719
|
+
addGroup(name, title, description) {
|
|
720
|
+
if (this.isGroupDefined(name)) {
|
|
721
|
+
return this.findGroup(name);
|
|
722
|
+
}
|
|
723
|
+
const group = {
|
|
724
|
+
name,
|
|
725
|
+
title,
|
|
726
|
+
description,
|
|
727
|
+
permissions: []
|
|
728
|
+
};
|
|
729
|
+
const builder = new AXPPermissionDefinitionGroupBuilder(this, group);
|
|
730
|
+
this.builders.set(name, builder);
|
|
731
|
+
return builder;
|
|
732
|
+
}
|
|
733
|
+
getGroupDefinitions() {
|
|
734
|
+
return Array.from(this.builders.values()).map(b => b.group);
|
|
735
|
+
}
|
|
736
|
+
findGroup(name) {
|
|
737
|
+
return this.builders.get(name);
|
|
738
|
+
}
|
|
739
|
+
isGroupDefined(name) {
|
|
740
|
+
return this.builders.has(name);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
class AXPPermissionDefinitionGroupBuilder {
|
|
744
|
+
get group() {
|
|
745
|
+
return this._group;
|
|
746
|
+
}
|
|
747
|
+
constructor(context, group) {
|
|
748
|
+
this.context = context;
|
|
749
|
+
this._group = group;
|
|
750
|
+
}
|
|
751
|
+
addPermission(name, title, description, requiredFeatures) {
|
|
752
|
+
const permission = {
|
|
753
|
+
name,
|
|
754
|
+
title,
|
|
755
|
+
description,
|
|
756
|
+
children: [],
|
|
757
|
+
requiredFeatures
|
|
758
|
+
};
|
|
759
|
+
this._group.permissions.push(permission);
|
|
760
|
+
return new AXPPermissionDefinitionBuilder(this, permission);
|
|
761
|
+
}
|
|
762
|
+
endGroup() {
|
|
763
|
+
return this.context;
|
|
764
|
+
}
|
|
765
|
+
findPermission(path) {
|
|
766
|
+
return undefined;
|
|
767
|
+
}
|
|
768
|
+
findGroup(name) {
|
|
769
|
+
return undefined;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
class AXPPermissionDefinitionBuilder {
|
|
773
|
+
constructor(groupBuilder, permission) {
|
|
774
|
+
this.groupBuilder = groupBuilder;
|
|
775
|
+
this.permission = permission;
|
|
776
|
+
}
|
|
777
|
+
addChild(name, title, description, requiredFeatures) {
|
|
778
|
+
const permission = {
|
|
779
|
+
name,
|
|
780
|
+
title,
|
|
781
|
+
description,
|
|
782
|
+
children: [],
|
|
783
|
+
requiredFeatures
|
|
784
|
+
};
|
|
785
|
+
this.permission.children.push(permission);
|
|
786
|
+
return this;
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Set required features for this permission.
|
|
790
|
+
* @param features - Array of feature names (e.g., ['PlatformManagement.menu-customization'])
|
|
791
|
+
*/
|
|
792
|
+
requireFeatures(...features) {
|
|
793
|
+
this.permission.requiredFeatures = features;
|
|
794
|
+
return this;
|
|
795
|
+
}
|
|
796
|
+
endPermission() {
|
|
797
|
+
return this.groupBuilder;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
const AXP_PERMISSION_DEFINITION_PROVIDER = new InjectionToken('AXP_PERMISSION_DEFINITION_PROVIDER', {
|
|
802
|
+
providedIn: 'root',
|
|
803
|
+
factory: () => {
|
|
804
|
+
return [];
|
|
805
|
+
}
|
|
806
|
+
});
|
|
807
|
+
class AXPPermissionDefinitionService {
|
|
808
|
+
constructor() {
|
|
809
|
+
this.providers = inject(AXP_PERMISSION_DEFINITION_PROVIDER, { optional: true });
|
|
810
|
+
this.sessionService = inject(AXPSessionService);
|
|
811
|
+
this.cache = null;
|
|
812
|
+
}
|
|
813
|
+
async load() {
|
|
814
|
+
if (this.cache) {
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
const context = new AXPPermissionDefinitionProviderContext();
|
|
818
|
+
// Load providers from DI tokens
|
|
819
|
+
if (Array.isArray(this.providers)) {
|
|
820
|
+
for (const provider of this.providers) {
|
|
821
|
+
if (provider instanceof Promise) {
|
|
822
|
+
// If provider is a promise, resolve it
|
|
823
|
+
const resolvedProvider = await provider;
|
|
824
|
+
await resolvedProvider.define(context);
|
|
825
|
+
}
|
|
826
|
+
else {
|
|
827
|
+
// If provider is a direct instance, use it directly
|
|
828
|
+
await provider.define(context);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
// Filter permissions based on required features
|
|
833
|
+
const allGroups = context.getGroupDefinitions();
|
|
834
|
+
this.cache = this.filterByFeatures(allGroups);
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Filter permissions based on required features.
|
|
838
|
+
* Removes permissions that have required features that are not enabled.
|
|
839
|
+
*/
|
|
840
|
+
filterByFeatures(groups) {
|
|
841
|
+
return groups.map(group => ({
|
|
842
|
+
...group,
|
|
843
|
+
permissions: this.filterPermissions(group.permissions)
|
|
844
|
+
})).filter(group => group.permissions.length > 0);
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Recursively filter permissions and their children based on required features.
|
|
848
|
+
*/
|
|
849
|
+
filterPermissions(permissions) {
|
|
850
|
+
return permissions
|
|
851
|
+
.filter(permission => {
|
|
852
|
+
// Check if required features are enabled
|
|
853
|
+
if (permission.requiredFeatures && permission.requiredFeatures.length > 0) {
|
|
854
|
+
const allFeaturesEnabled = permission.requiredFeatures.every(featureName => this.sessionService.isFeatureEnabled(featureName));
|
|
855
|
+
if (!allFeaturesEnabled) {
|
|
856
|
+
return false; // Filter out this permission
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
return true;
|
|
860
|
+
})
|
|
861
|
+
.map(permission => ({
|
|
862
|
+
...permission,
|
|
863
|
+
children: this.filterPermissions(permission.children)
|
|
864
|
+
}))
|
|
865
|
+
.filter(permission => {
|
|
866
|
+
// Remove permissions that have no children if they were only containers
|
|
867
|
+
// (This is optional - you might want to keep parent permissions even without children)
|
|
868
|
+
return true;
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
async reload() {
|
|
872
|
+
this.cache = null;
|
|
873
|
+
await this.load();
|
|
874
|
+
}
|
|
875
|
+
async getGroups() {
|
|
876
|
+
await this.load();
|
|
877
|
+
return this.cache ?? [];
|
|
878
|
+
}
|
|
879
|
+
async getPermissions() {
|
|
880
|
+
await this.load();
|
|
881
|
+
return this.cache?.flatMap(g => g.permissions) ?? [];
|
|
882
|
+
}
|
|
883
|
+
async getGroup(name) {
|
|
884
|
+
await this.load();
|
|
885
|
+
return this.cache?.find(g => g.name === name) ?? null;
|
|
886
|
+
}
|
|
887
|
+
async getPermission(name) {
|
|
888
|
+
await this.load();
|
|
889
|
+
return this.cache?.find(g => g.permissions.find(p => p.name === name))?.permissions.find(p => p.name === name) ?? null;
|
|
890
|
+
}
|
|
891
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPPermissionDefinitionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
892
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPPermissionDefinitionService, providedIn: 'root' }); }
|
|
893
|
+
}
|
|
894
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPPermissionDefinitionService, decorators: [{
|
|
895
|
+
type: Injectable,
|
|
896
|
+
args: [{ providedIn: 'root' }]
|
|
897
|
+
}] });
|
|
898
|
+
|
|
899
|
+
//#region ---- Imports ----
|
|
900
|
+
//#endregion
|
|
901
|
+
//#region ---- Helpers ----
|
|
902
|
+
function flattenPermissionDefinitions(nodes, groupPrefix) {
|
|
903
|
+
const rows = [];
|
|
904
|
+
for (const node of nodes) {
|
|
905
|
+
const displayTitle = groupPrefix ? `${groupPrefix} / ${node.title}` : node.title;
|
|
906
|
+
if (node.children?.length) {
|
|
907
|
+
rows.push(...flattenPermissionDefinitions(node.children, displayTitle));
|
|
908
|
+
}
|
|
909
|
+
else {
|
|
910
|
+
rows.push({
|
|
911
|
+
id: String(node.name),
|
|
912
|
+
title: displayTitle,
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
return rows;
|
|
917
|
+
}
|
|
918
|
+
//#endregion
|
|
919
|
+
//#region ---- Permission definitions data source ----
|
|
920
|
+
/**
|
|
921
|
+
* Registered permission definitions for select widgets via dataSource name {@link PERMISSION_DEFINITIONS_DATASOURCE_NAME}.
|
|
922
|
+
*/
|
|
923
|
+
const PERMISSION_DEFINITIONS_DATASOURCE_NAME = 'platform-permission-definitions';
|
|
924
|
+
/**
|
|
925
|
+
* Data source definition for leaf permissions from {@link AXPPermissionDefinitionService#getGroups}.
|
|
926
|
+
*/
|
|
927
|
+
class AXPPermissionDefinitionsDataSourceDefinition {
|
|
928
|
+
constructor() {
|
|
929
|
+
//#region ---- Services & Dependencies ----
|
|
930
|
+
this.permissionDefinitionService = inject(AXPPermissionDefinitionService);
|
|
931
|
+
//#endregion
|
|
932
|
+
}
|
|
933
|
+
//#endregion
|
|
934
|
+
//#region ---- Public API ----
|
|
935
|
+
async items() {
|
|
936
|
+
return [
|
|
937
|
+
{
|
|
938
|
+
name: PERMISSION_DEFINITIONS_DATASOURCE_NAME,
|
|
939
|
+
title: 'Permissions',
|
|
940
|
+
source: () => new AXDataSource({
|
|
941
|
+
key: 'id',
|
|
942
|
+
load: async () => {
|
|
943
|
+
const groups = await this.permissionDefinitionService.getGroups();
|
|
944
|
+
const list = [];
|
|
945
|
+
for (const g of groups) {
|
|
946
|
+
list.push(...flattenPermissionDefinitions(g.permissions, g.title));
|
|
947
|
+
}
|
|
948
|
+
return { items: list, total: list.length };
|
|
949
|
+
},
|
|
950
|
+
byKey: async (key) => {
|
|
951
|
+
const groups = await this.permissionDefinitionService.getGroups();
|
|
952
|
+
const list = [];
|
|
953
|
+
for (const g of groups) {
|
|
954
|
+
list.push(...flattenPermissionDefinitions(g.permissions, g.title));
|
|
955
|
+
}
|
|
956
|
+
return list.find((item) => item.id === key);
|
|
957
|
+
},
|
|
958
|
+
pageSize: 1000,
|
|
959
|
+
}),
|
|
960
|
+
columns: [
|
|
961
|
+
{
|
|
962
|
+
name: 'id',
|
|
963
|
+
title: 'ID',
|
|
964
|
+
datatype: 'string',
|
|
965
|
+
type: AXPWidgetsCatalog.text,
|
|
966
|
+
},
|
|
967
|
+
{
|
|
968
|
+
name: 'title',
|
|
969
|
+
title: 'Title',
|
|
970
|
+
datatype: 'string',
|
|
971
|
+
type: AXPWidgetsCatalog.text,
|
|
972
|
+
},
|
|
973
|
+
],
|
|
974
|
+
filters: [
|
|
975
|
+
{
|
|
976
|
+
field: 'title',
|
|
977
|
+
title: 'Title',
|
|
978
|
+
operator: { type: 'equal' },
|
|
979
|
+
widget: { type: AXPWidgetsCatalog.text },
|
|
980
|
+
filterType: { advance: true, inline: true },
|
|
981
|
+
},
|
|
982
|
+
],
|
|
983
|
+
textField: { name: 'title', title: 'Title' },
|
|
984
|
+
valueField: { name: 'id', title: 'ID' },
|
|
985
|
+
},
|
|
986
|
+
];
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
//#endregion
|
|
990
|
+
|
|
991
|
+
class AXPPermissionEvaluatorScopeProvider {
|
|
992
|
+
constructor() {
|
|
993
|
+
this.sessionService = inject(AXPSessionService);
|
|
994
|
+
}
|
|
995
|
+
async provide(context) {
|
|
996
|
+
context.addScope('permission', {
|
|
997
|
+
/**
|
|
998
|
+
* Check if the current user has all specified permissions
|
|
999
|
+
* @param permissions - Single permission string or array of permission strings
|
|
1000
|
+
* @returns boolean - true if user has all permissions, false otherwise
|
|
1001
|
+
*/
|
|
1002
|
+
check: (...permissions) => {
|
|
1003
|
+
return this.sessionService.authorize(...permissions);
|
|
1004
|
+
},
|
|
1005
|
+
/**
|
|
1006
|
+
* Check if the current user has any of the specified permissions
|
|
1007
|
+
* @param permissions - Array of permission strings
|
|
1008
|
+
* @returns boolean - true if user has any of the permissions, false otherwise
|
|
1009
|
+
*/
|
|
1010
|
+
checkAny: (...permissions) => {
|
|
1011
|
+
return permissions.some(p => this.sessionService.authorize(p));
|
|
1012
|
+
},
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
const AXPAuthGuard = (route, state) => {
|
|
1018
|
+
const sessionService = inject(AXPSessionService);
|
|
1019
|
+
return sessionService.isAuthenticatedWithLoading$.pipe(first(), map((value) => {
|
|
1020
|
+
if (value) {
|
|
1021
|
+
return true;
|
|
1022
|
+
}
|
|
1023
|
+
throw new AXPUnauthenticatedError(`Access denied. You are not currently logged in. Please log in to access this page. If you continue to see this message after logging in, please contact support.`, {
|
|
1024
|
+
redirectUrl: state.url,
|
|
1025
|
+
});
|
|
1026
|
+
}));
|
|
1027
|
+
};
|
|
1028
|
+
|
|
1029
|
+
function initializeAppState(service) {
|
|
1030
|
+
return async () => {
|
|
1031
|
+
try {
|
|
1032
|
+
await service.restoreSession();
|
|
1033
|
+
}
|
|
1034
|
+
catch (error) {
|
|
1035
|
+
console.error(error);
|
|
1036
|
+
}
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
class AXPAuthModule {
|
|
1040
|
+
static forRoot(configs) {
|
|
1041
|
+
return {
|
|
1042
|
+
ngModule: AXPAuthModule,
|
|
1043
|
+
providers: [
|
|
1044
|
+
...(configs?.strategies || []),
|
|
1045
|
+
{
|
|
1046
|
+
provide: 'AXPAuthModuleFactory',
|
|
1047
|
+
useFactory: (registry) => () => {
|
|
1048
|
+
registry.register(...(configs?.strategies || []));
|
|
1049
|
+
},
|
|
1050
|
+
deps: [AXPAuthStrategyRegistryService],
|
|
1051
|
+
multi: true,
|
|
1052
|
+
},
|
|
1053
|
+
],
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
static forChild(configs) {
|
|
1057
|
+
return {
|
|
1058
|
+
ngModule: AXPAuthModule,
|
|
1059
|
+
providers: [
|
|
1060
|
+
...(configs?.strategies || []),
|
|
1061
|
+
{
|
|
1062
|
+
provide: 'AXPAuthModuleFactory',
|
|
1063
|
+
useFactory: (registry) => () => {
|
|
1064
|
+
registry.register(...(configs?.strategies || []));
|
|
1065
|
+
},
|
|
1066
|
+
deps: [AXPAuthStrategyRegistryService],
|
|
1067
|
+
multi: true,
|
|
1068
|
+
},
|
|
1069
|
+
],
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* @ignore
|
|
1074
|
+
*/
|
|
1075
|
+
constructor(instances) {
|
|
1076
|
+
instances?.forEach((f) => {
|
|
1077
|
+
f();
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPAuthModule, deps: [{ token: 'AXPAuthModuleFactory', optional: true }], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
1081
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: AXPAuthModule, declarations: [AXPPermissionDirective, AXPFeatureDirective], exports: [AXPPermissionDirective, AXPFeatureDirective] }); }
|
|
1082
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPAuthModule, providers: [
|
|
1083
|
+
provideAppInitializer(() => {
|
|
1084
|
+
const initializerFn = initializeAppState(inject(AXPSessionService));
|
|
1085
|
+
return initializerFn();
|
|
1086
|
+
}),
|
|
1087
|
+
{
|
|
1088
|
+
provide: AXP_SESSION_SERVICE,
|
|
1089
|
+
useExisting: AXPSessionService,
|
|
1090
|
+
},
|
|
1091
|
+
] }); }
|
|
1092
|
+
}
|
|
1093
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPAuthModule, decorators: [{
|
|
1094
|
+
type: NgModule,
|
|
1095
|
+
args: [{
|
|
1096
|
+
imports: [],
|
|
1097
|
+
exports: [AXPPermissionDirective, AXPFeatureDirective],
|
|
1098
|
+
declarations: [AXPPermissionDirective, AXPFeatureDirective],
|
|
1099
|
+
providers: [
|
|
1100
|
+
provideAppInitializer(() => {
|
|
1101
|
+
const initializerFn = initializeAppState(inject(AXPSessionService));
|
|
1102
|
+
return initializerFn();
|
|
1103
|
+
}),
|
|
1104
|
+
{
|
|
1105
|
+
provide: AXP_SESSION_SERVICE,
|
|
1106
|
+
useExisting: AXPSessionService,
|
|
1107
|
+
},
|
|
1108
|
+
],
|
|
1109
|
+
}]
|
|
1110
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
1111
|
+
type: Optional
|
|
1112
|
+
}, {
|
|
1113
|
+
type: Inject,
|
|
1114
|
+
args: ['AXPAuthModuleFactory']
|
|
1115
|
+
}] }] });
|
|
1116
|
+
|
|
1117
|
+
class AXPAuthStrategy {
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
//#region ---- JWT Utility ----
|
|
1121
|
+
/**
|
|
1122
|
+
* Utility class for JWT token operations
|
|
1123
|
+
*/
|
|
1124
|
+
class JwtUtil {
|
|
1125
|
+
/**
|
|
1126
|
+
* Parses a JWT token and returns the payload
|
|
1127
|
+
*/
|
|
1128
|
+
static parseJwt(token) {
|
|
1129
|
+
try {
|
|
1130
|
+
const base64Url = token.split('.')[1];
|
|
1131
|
+
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
|
1132
|
+
const jsonPayload = decodeURIComponent(atob(base64)
|
|
1133
|
+
.split('')
|
|
1134
|
+
.map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
|
|
1135
|
+
.join(''));
|
|
1136
|
+
return JSON.parse(jsonPayload);
|
|
1137
|
+
}
|
|
1138
|
+
catch (error) {
|
|
1139
|
+
throw new Error('Invalid JWT token');
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
//#endregion
|
|
1144
|
+
//#region ---- PKCE Utility ----
|
|
1145
|
+
/**
|
|
1146
|
+
* Utility class for PKCE (Proof Key for Code Exchange) operations
|
|
1147
|
+
*/
|
|
1148
|
+
class PkceUtil {
|
|
1149
|
+
/**
|
|
1150
|
+
* Generates a random string for PKCE code verifier
|
|
1151
|
+
*/
|
|
1152
|
+
static generateRandomString(length) {
|
|
1153
|
+
const array = new Uint8Array(length);
|
|
1154
|
+
crypto.getRandomValues(array);
|
|
1155
|
+
return this.base64UrlEncode(array);
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Generates PKCE code challenge from verifier
|
|
1159
|
+
*/
|
|
1160
|
+
static async generateCodeChallenge(codeVerifier) {
|
|
1161
|
+
const encoder = new TextEncoder();
|
|
1162
|
+
const data = encoder.encode(codeVerifier);
|
|
1163
|
+
const digest = await crypto.subtle.digest('SHA-256', data);
|
|
1164
|
+
return this.base64UrlEncode(new Uint8Array(digest));
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Base64 URL encoding for PKCE
|
|
1168
|
+
*/
|
|
1169
|
+
static base64UrlEncode(array) {
|
|
1170
|
+
return btoa(String.fromCharCode(...array))
|
|
1171
|
+
.replace(/\+/g, '-')
|
|
1172
|
+
.replace(/\//g, '_')
|
|
1173
|
+
.replace(/=/g, '');
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
//#endregion
|
|
1177
|
+
//#region ---- Time Utility ----
|
|
1178
|
+
/**
|
|
1179
|
+
* Utility class for time and date operations
|
|
1180
|
+
*/
|
|
1181
|
+
class TimeUtil {
|
|
1182
|
+
/**
|
|
1183
|
+
* Calculates the time difference in milliseconds between a future date and now
|
|
1184
|
+
*/
|
|
1185
|
+
static expiresInMilliseconds(expiresInDate) {
|
|
1186
|
+
return new Date(expiresInDate).getTime() - new Date().getTime();
|
|
1187
|
+
}
|
|
1188
|
+
/**
|
|
1189
|
+
* Calculates expiration date from seconds
|
|
1190
|
+
*/
|
|
1191
|
+
static calculateExpireInDate(expireInSeconds) {
|
|
1192
|
+
return new Date(Date.now() + expireInSeconds * 1000).toISOString();
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
//#endregion
|
|
1196
|
+
|
|
1197
|
+
//#region ---- Challenge Data Types ----
|
|
1198
|
+
//#endregion
|
|
1199
|
+
|
|
1200
|
+
//#region ---- Abstract Challenge Provider ----
|
|
1201
|
+
/**
|
|
1202
|
+
* Abstract base class for login challenge providers
|
|
1203
|
+
*
|
|
1204
|
+
* Implement this class to create custom challenge mechanisms like:
|
|
1205
|
+
* - Image CAPTCHA
|
|
1206
|
+
* - reCAPTCHA
|
|
1207
|
+
* - SMS verification
|
|
1208
|
+
* - Email verification
|
|
1209
|
+
*
|
|
1210
|
+
* @example
|
|
1211
|
+
* ```typescript
|
|
1212
|
+
* @Injectable()
|
|
1213
|
+
* export class MyImageCaptchaProvider extends AXPLoginChallengeProvider {
|
|
1214
|
+
* readonly name = 'image-captcha';
|
|
1215
|
+
*
|
|
1216
|
+
* checkResponse(error: unknown): AXPChallengeCheckResult | null {
|
|
1217
|
+
* if (error instanceof HttpErrorResponse) {
|
|
1218
|
+
* if (error.error?.requiresCaptcha) {
|
|
1219
|
+
* return { required: true };
|
|
1220
|
+
* }
|
|
1221
|
+
* }
|
|
1222
|
+
* return null;
|
|
1223
|
+
* }
|
|
1224
|
+
*
|
|
1225
|
+
* async getChallenge(): Promise<AXPLoginChallengeData> {
|
|
1226
|
+
* const response = await this.http.get('/api/captcha').toPromise();
|
|
1227
|
+
* return {
|
|
1228
|
+
* id: response.id,
|
|
1229
|
+
* content: response.image,
|
|
1230
|
+
* contentType: 'image-base64'
|
|
1231
|
+
* };
|
|
1232
|
+
* }
|
|
1233
|
+
*
|
|
1234
|
+
* async refreshChallenge(): Promise<AXPLoginChallengeData> {
|
|
1235
|
+
* return this.getChallenge();
|
|
1236
|
+
* }
|
|
1237
|
+
*
|
|
1238
|
+
* getChallengeComponent(): Type<AXPLoginChallengeComponentBase> {
|
|
1239
|
+
* return MyCaptchaChallengeComponent;
|
|
1240
|
+
* }
|
|
1241
|
+
* }
|
|
1242
|
+
* ```
|
|
1243
|
+
*/
|
|
1244
|
+
class AXPLoginChallengeProvider {
|
|
1245
|
+
/**
|
|
1246
|
+
* Returns the component type for rendering the challenge UI
|
|
1247
|
+
*
|
|
1248
|
+
* Override this method to provide a custom challenge UI component.
|
|
1249
|
+
* If not overridden (returns null), the login component will use
|
|
1250
|
+
* a default built-in UI.
|
|
1251
|
+
*
|
|
1252
|
+
* @returns Component type extending AXPLoginChallengeComponentBase, or null for default UI
|
|
1253
|
+
*/
|
|
1254
|
+
getChallengeComponent() {
|
|
1255
|
+
return null;
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
//#endregion
|
|
1259
|
+
|
|
1260
|
+
//#region ---- Injection Token ----
|
|
1261
|
+
/**
|
|
1262
|
+
* Injection token for the login challenge provider
|
|
1263
|
+
*
|
|
1264
|
+
* This token is optional - if not provided, no challenge mechanism will be used.
|
|
1265
|
+
*
|
|
1266
|
+
* @example
|
|
1267
|
+
* ```typescript
|
|
1268
|
+
* // In your app module or provider configuration:
|
|
1269
|
+
* providers: [
|
|
1270
|
+
* {
|
|
1271
|
+
* provide: AXP_LOGIN_CHALLENGE_PROVIDER,
|
|
1272
|
+
* useClass: MyImageCaptchaProvider
|
|
1273
|
+
* }
|
|
1274
|
+
* ]
|
|
1275
|
+
*
|
|
1276
|
+
* // In a component:
|
|
1277
|
+
* private challengeProvider = inject(AXP_LOGIN_CHALLENGE_PROVIDER, { optional: true });
|
|
1278
|
+
* ```
|
|
1279
|
+
*/
|
|
1280
|
+
const AXP_LOGIN_CHALLENGE_PROVIDER = new InjectionToken('AXP_LOGIN_CHALLENGE_PROVIDER');
|
|
1281
|
+
//#endregion
|
|
1282
|
+
|
|
1283
|
+
//#region ---- Base Challenge Component ----
|
|
1284
|
+
/**
|
|
1285
|
+
* Base class for login challenge UI components
|
|
1286
|
+
*
|
|
1287
|
+
* Providers can extend this class to create custom challenge UIs.
|
|
1288
|
+
* The login component will render this component and listen to its outputs.
|
|
1289
|
+
*
|
|
1290
|
+
* @example
|
|
1291
|
+
* ```typescript
|
|
1292
|
+
* @Component({
|
|
1293
|
+
* selector: 'my-captcha-challenge',
|
|
1294
|
+
* template: `
|
|
1295
|
+
* <div class="captcha-container">
|
|
1296
|
+
* <img [src]="'data:image/png;base64,' + challengeData().content" />
|
|
1297
|
+
* <input
|
|
1298
|
+
* type="text"
|
|
1299
|
+
* [value]="response()"
|
|
1300
|
+
* (input)="onResponseChange($event)"
|
|
1301
|
+
* />
|
|
1302
|
+
* <button (click)="onRefreshClick()">Refresh</button>
|
|
1303
|
+
* </div>
|
|
1304
|
+
* `
|
|
1305
|
+
* })
|
|
1306
|
+
* export class MyCaptchaChallengeComponent extends AXPLoginChallengeComponentBase {
|
|
1307
|
+
* response = signal('');
|
|
1308
|
+
*
|
|
1309
|
+
* onResponseChange(event: Event) {
|
|
1310
|
+
* const value = (event.target as HTMLInputElement).value;
|
|
1311
|
+
* this.response.set(value);
|
|
1312
|
+
* this.responseChange.emit(value);
|
|
1313
|
+
* }
|
|
1314
|
+
*
|
|
1315
|
+
* onRefreshClick() {
|
|
1316
|
+
* this.refreshRequest.emit();
|
|
1317
|
+
* }
|
|
1318
|
+
* }
|
|
1319
|
+
* ```
|
|
1320
|
+
*/
|
|
1321
|
+
class AXPLoginChallengeComponentBase {
|
|
1322
|
+
constructor() {
|
|
1323
|
+
//#region ---- Inputs ----
|
|
1324
|
+
/**
|
|
1325
|
+
* Challenge data to display
|
|
1326
|
+
* Contains the image/content and metadata from the server
|
|
1327
|
+
*/
|
|
1328
|
+
this.challengeData = input.required(...(ngDevMode ? [{ debugName: "challengeData" }] : /* istanbul ignore next */ []));
|
|
1329
|
+
/**
|
|
1330
|
+
* Whether the challenge is currently loading (e.g., refreshing)
|
|
1331
|
+
*/
|
|
1332
|
+
this.isLoading = input(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
|
|
1333
|
+
//#endregion
|
|
1334
|
+
//#region ---- Outputs ----
|
|
1335
|
+
/**
|
|
1336
|
+
* Emits when the user enters or changes their response
|
|
1337
|
+
* The login component will capture this value and include it in credentials
|
|
1338
|
+
*/
|
|
1339
|
+
this.responseChange = output();
|
|
1340
|
+
/**
|
|
1341
|
+
* Emits when the user requests a new challenge (e.g., clicks refresh button)
|
|
1342
|
+
* The login component will call provider.refreshChallenge()
|
|
1343
|
+
*/
|
|
1344
|
+
this.refreshRequest = output();
|
|
1345
|
+
}
|
|
1346
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLoginChallengeComponentBase, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1347
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.9", type: AXPLoginChallengeComponentBase, isStandalone: true, selector: "ng-component", inputs: { challengeData: { classPropertyName: "challengeData", publicName: "challengeData", isSignal: true, isRequired: true, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { responseChange: "responseChange", refreshRequest: "refreshRequest" }, ngImport: i0, template: '', isInline: true }); }
|
|
1348
|
+
}
|
|
1349
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLoginChallengeComponentBase, decorators: [{
|
|
1350
|
+
type: Component,
|
|
1351
|
+
args: [{
|
|
1352
|
+
template: '',
|
|
1353
|
+
standalone: true,
|
|
1354
|
+
}]
|
|
1355
|
+
}], propDecorators: { challengeData: [{ type: i0.Input, args: [{ isSignal: true, alias: "challengeData", required: true }] }], isLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLoading", required: false }] }], responseChange: [{ type: i0.Output, args: ["responseChange"] }], refreshRequest: [{ type: i0.Output, args: ["refreshRequest"] }] } });
|
|
1356
|
+
|
|
1357
|
+
/**
|
|
1358
|
+
* Generated bundle index. Do not edit.
|
|
1359
|
+
*/
|
|
1360
|
+
|
|
1361
|
+
export { AXPAuthGuard, AXPAuthModule, AXPAuthStrategy, AXPAuthStrategyRegistryService, AXPFeatureDirective, AXPFeatureGuard, AXPLoginChallengeComponentBase, AXPLoginChallengeProvider, AXPPermissionDefinitionBuilder, AXPPermissionDefinitionGroupBuilder, AXPPermissionDefinitionProviderContext, AXPPermissionDefinitionService, AXPPermissionDefinitionsDataSourceDefinition, AXPPermissionDirective, AXPPermissionEvaluatorScopeProvider, AXPPermissionGuard, AXPSessionContext, AXPSessionService, AXPSessionStatus, AXPUnauthenticatedError, AXPUnauthorizedError, AXP_APPLICATION_LOADER, AXP_FEATURE_CHECKER, AXP_FEATURE_LOADER, AXP_LOGIN_CHALLENGE_PROVIDER, AXP_PERMISSION_CHECKER, AXP_PERMISSION_DEFINITION_PROVIDER, AXP_PERMISSION_LOADER, AXP_TENANT_LOADER, JwtUtil, PERMISSION_DEFINITIONS_DATASOURCE_NAME, PkceUtil, TimeUtil, initializeAppState };
|
|
1362
|
+
//# sourceMappingURL=acorex-platform-auth.mjs.map
|