@nauticana/sail 0.5.0
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 +201 -0
- package/README.md +741 -0
- package/package.json +58 -0
- package/src/component/abstract/base_async.ts +49 -0
- package/src/component/abstract/base_form.ts +11 -0
- package/src/component/abstract/base_table.ts +320 -0
- package/src/component/abstract/base_view.ts +93 -0
- package/src/component/billing/checkout_button.html +10 -0
- package/src/component/billing/checkout_button.ts +57 -0
- package/src/component/billing/payment_methods.html +27 -0
- package/src/component/billing/payment_methods.ts +36 -0
- package/src/component/billing/plan_selector.html +24 -0
- package/src/component/billing/plan_selector.ts +29 -0
- package/src/component/form/form_field.html +54 -0
- package/src/component/form/form_field.ts +156 -0
- package/src/component/form/form_record.html +14 -0
- package/src/component/form/form_record.ts +56 -0
- package/src/component/form/form_table.html +19 -0
- package/src/component/form/form_table.ts +58 -0
- package/src/component/login/account_deletion.html +42 -0
- package/src/component/login/account_deletion.ts +79 -0
- package/src/component/login/chpass_component.html +58 -0
- package/src/component/login/chpass_component.ts +96 -0
- package/src/component/login/confirm_chpass_component.html +47 -0
- package/src/component/login/confirm_chpass_component.ts +70 -0
- package/src/component/login/confirm_register_component.html +37 -0
- package/src/component/login/confirm_register_component.ts +71 -0
- package/src/component/login/consent_gate.html +30 -0
- package/src/component/login/consent_gate.ts +109 -0
- package/src/component/login/login_component.html +43 -0
- package/src/component/login/login_component.ts +73 -0
- package/src/component/login/otp_input.html +38 -0
- package/src/component/login/otp_input.ts +104 -0
- package/src/component/login/register_component.html +132 -0
- package/src/component/login/register_component.ts +184 -0
- package/src/component/login/social_login.html +13 -0
- package/src/component/login/social_login.ts +120 -0
- package/src/component/navigation/navigation.html +49 -0
- package/src/component/navigation/navigation.ts +69 -0
- package/src/component/security/trusted_devices.html +76 -0
- package/src/component/security/trusted_devices.ts +92 -0
- package/src/component/security/twofactor_setup.html +75 -0
- package/src/component/security/twofactor_setup.ts +106 -0
- package/src/component/security/twofactor_verify.html +45 -0
- package/src/component/security/twofactor_verify.ts +90 -0
- package/src/component/table/table_detail.html +53 -0
- package/src/component/table/table_detail.ts +133 -0
- package/src/component/table/table_edit.html +58 -0
- package/src/component/table/table_edit.ts +163 -0
- package/src/component/table/table_list.html +41 -0
- package/src/component/table/table_list.ts +100 -0
- package/src/component/table/table_lookup.html +35 -0
- package/src/component/table/table_lookup.ts +88 -0
- package/src/component/table/table_search.html +17 -0
- package/src/component/table/table_search.ts +80 -0
- package/src/config.ts +67 -0
- package/src/index.ts +41 -0
- package/src/model/appdata.ts +266 -0
- package/src/model/auth.ts +134 -0
- package/src/model/common.ts +115 -0
- package/src/model/decorator.ts +74 -0
- package/src/service/api-response.interceptor.ts +26 -0
- package/src/service/auth.interceptor.ts +11 -0
- package/src/service/auth.service.ts +576 -0
- package/src/service/billing.service.ts +65 -0
- package/src/service/label.service.ts +18 -0
- package/src/service/rest_service.ts +94 -0
- package/src/service/rest_url.ts +42 -0
- package/src/service/script_loader.ts +31 -0
- package/tsconfig.json +16 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export * from './config';
|
|
2
|
+
export * from './model/appdata';
|
|
3
|
+
export * from './model/auth';
|
|
4
|
+
export * from './model/common';
|
|
5
|
+
export * from './model/decorator';
|
|
6
|
+
export * from './component/abstract/base_table';
|
|
7
|
+
export * from './component/abstract/base_form';
|
|
8
|
+
export * from './component/abstract/base_view';
|
|
9
|
+
export * from './component/abstract/base_async';
|
|
10
|
+
export * from './component/form/form_field';
|
|
11
|
+
export * from './component/form/form_record';
|
|
12
|
+
export * from './component/form/form_table';
|
|
13
|
+
export * from './component/table/table_list';
|
|
14
|
+
export * from './component/table/table_search';
|
|
15
|
+
export * from './component/table/table_edit';
|
|
16
|
+
export * from './component/table/table_detail';
|
|
17
|
+
export * from './component/table/table_lookup';
|
|
18
|
+
export * from './component/navigation/navigation';
|
|
19
|
+
export * from './component/login/login_component';
|
|
20
|
+
export * from './component/login/register_component';
|
|
21
|
+
export * from './component/login/chpass_component';
|
|
22
|
+
export * from './component/login/confirm_register_component';
|
|
23
|
+
export * from './component/login/confirm_chpass_component';
|
|
24
|
+
export * from './component/login/consent_gate';
|
|
25
|
+
export * from './component/login/otp_input';
|
|
26
|
+
export * from './component/login/social_login';
|
|
27
|
+
export * from './component/login/account_deletion';
|
|
28
|
+
export * from './component/security/twofactor_setup';
|
|
29
|
+
export * from './component/security/twofactor_verify';
|
|
30
|
+
export * from './component/security/trusted_devices';
|
|
31
|
+
export * from './component/billing/plan_selector';
|
|
32
|
+
export * from './component/billing/checkout_button';
|
|
33
|
+
export * from './component/billing/payment_methods';
|
|
34
|
+
export * from './service/auth.service';
|
|
35
|
+
export * from './service/billing.service';
|
|
36
|
+
export * from './service/script_loader';
|
|
37
|
+
export * from './service/auth.interceptor';
|
|
38
|
+
export * from './service/api-response.interceptor';
|
|
39
|
+
export * from './service/rest_service';
|
|
40
|
+
export * from './service/rest_url';
|
|
41
|
+
export * from './service/label.service';
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { AbstractControlOptions } from '@angular/forms';
|
|
2
|
+
import { ApplicationMenu, AuthorizationRolePermission } from './common';
|
|
3
|
+
|
|
4
|
+
export const DataType = {
|
|
5
|
+
STRING: 'string',
|
|
6
|
+
INTEGER: 'integer',
|
|
7
|
+
FLOAT: 'float',
|
|
8
|
+
BOOLEAN: 'boolean',
|
|
9
|
+
TIMESTAMP: 'timestamp',
|
|
10
|
+
} as const;
|
|
11
|
+
|
|
12
|
+
export interface LoginRequest {
|
|
13
|
+
Username: string;
|
|
14
|
+
Password: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface LoginResponse {
|
|
18
|
+
token: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface AuthSummary {
|
|
22
|
+
Obj: string;
|
|
23
|
+
Act: string;
|
|
24
|
+
Val: string;
|
|
25
|
+
Reg: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ApplicationData {
|
|
29
|
+
MainMenu: ApplicationMenu[];
|
|
30
|
+
Permissions: AuthorizationRolePermission[];
|
|
31
|
+
ConstantCache: {[key: string]: {[key: string]: string}};
|
|
32
|
+
TableCache: {[key: string]: {[key: string]: string}};
|
|
33
|
+
TableDefinitions: {[key: string]: TableDefinition};
|
|
34
|
+
Apis: {[key: string]: DictionaryPath};
|
|
35
|
+
[key: string]: unknown;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface TableColumn {
|
|
39
|
+
ColumnName: string;
|
|
40
|
+
PascalName: string;
|
|
41
|
+
Caption: string;
|
|
42
|
+
DataType: string;
|
|
43
|
+
InputType: 'text' | 'password' | 'number' | 'date' | 'datetime' | 'datetime-local' | 'textarea' | 'select' | 'checkbox';
|
|
44
|
+
Size: number;
|
|
45
|
+
Scale: number;
|
|
46
|
+
Step: string;
|
|
47
|
+
Order: number;
|
|
48
|
+
Required: boolean;
|
|
49
|
+
IsKey: boolean;
|
|
50
|
+
SequenceName: string;
|
|
51
|
+
LookupDomain: string;
|
|
52
|
+
LookupTable: string;
|
|
53
|
+
LookupStyle: string;
|
|
54
|
+
HasDefault: boolean;
|
|
55
|
+
DefaultValue: string;
|
|
56
|
+
Validators?: AbstractControlOptions['validators'];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface TableAction {
|
|
60
|
+
action: string;
|
|
61
|
+
caption: string;
|
|
62
|
+
method: string;
|
|
63
|
+
enable: string;
|
|
64
|
+
authorityCheck: string;
|
|
65
|
+
recordSpecific: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface ForeignKey {
|
|
69
|
+
ParentTable: string;
|
|
70
|
+
ChildTable: string;
|
|
71
|
+
PascalName: string;
|
|
72
|
+
ConstraintName: string;
|
|
73
|
+
LookupStyle: string;
|
|
74
|
+
Columns: TableColumn[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface TableDefinition {
|
|
78
|
+
TableName: string;
|
|
79
|
+
PascalName: string;
|
|
80
|
+
PartnerSpecific: boolean;
|
|
81
|
+
Keys: TableColumn[];
|
|
82
|
+
Columns: TableColumn[];
|
|
83
|
+
Actions: TableAction[];
|
|
84
|
+
LookupStyle: string;
|
|
85
|
+
LookupColumns: TableColumn[];
|
|
86
|
+
Details: string[];
|
|
87
|
+
Parents: ForeignKey[];
|
|
88
|
+
Children: ForeignKey[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface DictionaryPath {
|
|
92
|
+
RestAPI: string;
|
|
93
|
+
Version: string;
|
|
94
|
+
Table: TableDefinition;
|
|
95
|
+
Caption: string;
|
|
96
|
+
PascalName: string;
|
|
97
|
+
PathType: string;
|
|
98
|
+
ParentKeys: TableColumn[];
|
|
99
|
+
ChildKeys: TableColumn[];
|
|
100
|
+
Children: DictionaryPath[];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface ReportParam {
|
|
104
|
+
Name: string;
|
|
105
|
+
DataType: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface RestReport {
|
|
109
|
+
Id: string;
|
|
110
|
+
Version: string;
|
|
111
|
+
QueryName: string;
|
|
112
|
+
Params: ReportParam[];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 2FA / Trusted device types
|
|
116
|
+
|
|
117
|
+
export interface LoginResponse2FA {
|
|
118
|
+
token: string;
|
|
119
|
+
twoFactorRequired: boolean;
|
|
120
|
+
loginToken?: string;
|
|
121
|
+
userId?: number;
|
|
122
|
+
partnerId?: number;
|
|
123
|
+
// Populated for users with the single-device session policy enabled.
|
|
124
|
+
// UI surfaces an info banner / can enforce "signed out elsewhere" notices.
|
|
125
|
+
singleDeviceSession?: boolean;
|
|
126
|
+
previousDeviceRevoked?: boolean;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface TwoFactorSetupResponse {
|
|
130
|
+
secret: string;
|
|
131
|
+
qrUri: string;
|
|
132
|
+
backupCodes: string[];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface TwoFactorVerifyRequest {
|
|
136
|
+
code: string;
|
|
137
|
+
loginToken?: string;
|
|
138
|
+
trustDevice?: boolean;
|
|
139
|
+
deviceFingerprint?: string;
|
|
140
|
+
deviceName?: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface TwoFactorVerifyResponse {
|
|
144
|
+
valid: boolean;
|
|
145
|
+
token?: string;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface TrustedDevice {
|
|
149
|
+
id: number;
|
|
150
|
+
name: string;
|
|
151
|
+
fingerprint: string;
|
|
152
|
+
last_used_at: string;
|
|
153
|
+
created_at: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface PartnerRegistration {
|
|
157
|
+
FirstName: string;
|
|
158
|
+
LastName: string;
|
|
159
|
+
UserName: string;
|
|
160
|
+
Email: string;
|
|
161
|
+
Password: string;
|
|
162
|
+
PartnerCaption: string;
|
|
163
|
+
Address: string;
|
|
164
|
+
City: string;
|
|
165
|
+
State: string;
|
|
166
|
+
Zipcode: string;
|
|
167
|
+
Country: string;
|
|
168
|
+
Phone: string;
|
|
169
|
+
Latitude: number;
|
|
170
|
+
Longitude: number;
|
|
171
|
+
DomainURL: string;
|
|
172
|
+
PlanID: string;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// PublicPlan — shape returned by GET /public/plans. Used on the registration
|
|
176
|
+
// page to render the plan picker and to drive `<app-checkout-button>` flows.
|
|
177
|
+
export interface PublicPlan {
|
|
178
|
+
id: string;
|
|
179
|
+
caption: string;
|
|
180
|
+
monthlyCost: number;
|
|
181
|
+
annualCost: number;
|
|
182
|
+
// Stripe price ID (or provider-equivalent). Optional because keel only
|
|
183
|
+
// populates it when a plan is wired to a billing provider; FREE plans and
|
|
184
|
+
// not-yet-priced tiers leave it empty. Pass straight to
|
|
185
|
+
// `<app-checkout-button [priceId]>` / `BillingService.createCheckout({ priceId })`.
|
|
186
|
+
priceId?: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ConfirmRegisterResponse — shape returned by POST /public/confirm/register.
|
|
190
|
+
// When paymentRequired is true the UI should redirect the user to paymentUrl
|
|
191
|
+
// before letting them log in.
|
|
192
|
+
export interface ConfirmRegisterResponse {
|
|
193
|
+
status: string;
|
|
194
|
+
partnerId: number;
|
|
195
|
+
planId: string;
|
|
196
|
+
paymentRequired: boolean;
|
|
197
|
+
paymentUrl?: string;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ── Billing / Payment types ──
|
|
201
|
+
// See keel/SHARED_PAYMENT.md for the backend contract.
|
|
202
|
+
|
|
203
|
+
export interface PaymentMethod {
|
|
204
|
+
id: string;
|
|
205
|
+
provider: string; // 'stripe' | 'lemonsqueezy' | ...
|
|
206
|
+
methodType: string; // 'card' | 'bank' | ...
|
|
207
|
+
last4?: string;
|
|
208
|
+
isDefault: boolean;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// CreateCheckout is JWT-gated and validates `priceId`, `successUrl`,
|
|
212
|
+
// `cancelUrl` against server-side allowlists. Empty allowlists cause every
|
|
213
|
+
// checkout to be rejected with 400.
|
|
214
|
+
export type CheckoutMode = 'subscription' | 'payment' | 'setup';
|
|
215
|
+
|
|
216
|
+
export interface CheckoutRequest {
|
|
217
|
+
mode?: CheckoutMode; // default 'subscription'
|
|
218
|
+
// Required for 'subscription' / 'payment'; must be empty for 'setup'.
|
|
219
|
+
// Pass `PublicPlan.priceId` straight through.
|
|
220
|
+
priceId?: string;
|
|
221
|
+
quantity?: number;
|
|
222
|
+
email?: string;
|
|
223
|
+
// Hostname must match keel's `AllowedRedirectHosts`. Default port matching
|
|
224
|
+
// is hostname-only; include the port (e.g. `host:8443`) only if the
|
|
225
|
+
// operator deliberately wants port-strict matching.
|
|
226
|
+
successUrl: string;
|
|
227
|
+
cancelUrl: string;
|
|
228
|
+
// Provider metadata — values must be strings. keel stringifies any
|
|
229
|
+
// numeric / boolean values in its server-side metadata adapter, so when
|
|
230
|
+
// reading back metadata in webhook handlers, expect the stringified form
|
|
231
|
+
// (e.g. `partner_id` arrives as `"42"`, not `42`).
|
|
232
|
+
metadata?: { [key: string]: string };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export interface CheckoutResponse {
|
|
236
|
+
checkoutUrl: string;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// REST list endpoints return paginated wrappers. `BackendService.list<T>()`
|
|
240
|
+
// unwraps to the array; use `listPaginated<T>()` to get the metadata.
|
|
241
|
+
export interface PaginatedList<T> {
|
|
242
|
+
items: T[];
|
|
243
|
+
limit: number;
|
|
244
|
+
offset: number;
|
|
245
|
+
total: number;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export interface Subscription {
|
|
249
|
+
planId: string;
|
|
250
|
+
status: string; // 'A' active, 'P' pending-payment, 'C' cancelled, ...
|
|
251
|
+
begda: string;
|
|
252
|
+
endda?: string;
|
|
253
|
+
monthlyCost: number;
|
|
254
|
+
currency: string;
|
|
255
|
+
autoRenew: boolean;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export interface Invoice {
|
|
259
|
+
id: string;
|
|
260
|
+
number: string;
|
|
261
|
+
status: string; // 'paid' | 'open' | 'void' | ...
|
|
262
|
+
total: number;
|
|
263
|
+
currency: string;
|
|
264
|
+
issuedAt: string;
|
|
265
|
+
paidAt?: string;
|
|
266
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// Auth / consent / OTP / social / push / re-auth types — mirror keel's port types
|
|
2
|
+
// (see keel/README.md and keel/handler/*).
|
|
3
|
+
|
|
4
|
+
// ── Canonical consent type labels (match keel's port.ConsentType*) ──
|
|
5
|
+
export const ConsentType = {
|
|
6
|
+
PRIVACY_POLICY: 'privacy_policy',
|
|
7
|
+
TERMS: 'terms',
|
|
8
|
+
CROSS_BORDER: 'cross_border',
|
|
9
|
+
VIDEO_OPT_IN: 'video_opt_in',
|
|
10
|
+
VIDEO_SESSION: 'video_session',
|
|
11
|
+
MARKETING: 'marketing',
|
|
12
|
+
} as const;
|
|
13
|
+
export type ConsentTypeValue = typeof ConsentType[keyof typeof ConsentType];
|
|
14
|
+
|
|
15
|
+
// Declarative config for an optional consent checkbox rendered by ConsentGate.
|
|
16
|
+
export interface ConsentOption {
|
|
17
|
+
id: string; // ConsentType value, e.g. 'video_opt_in'
|
|
18
|
+
label: string; // Checkbox label
|
|
19
|
+
required?: boolean; // If true, state.valid stays false until checked
|
|
20
|
+
hint?: string; // Small explanatory text below the label
|
|
21
|
+
linkUrl?: string; // Optional inline link in the label
|
|
22
|
+
linkText?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Emitted by ConsentGateComponent on every change. Mirrors keel's
|
|
26
|
+
// port.SignupConsent.Consents map directly.
|
|
27
|
+
export interface ConsentState {
|
|
28
|
+
consents: Record<string, boolean>;
|
|
29
|
+
policyVersion: string;
|
|
30
|
+
policyLanguage: string;
|
|
31
|
+
valid: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Sent to the backend alongside OTP / social / register requests.
|
|
35
|
+
// Matches keel's user.SignupConsent.
|
|
36
|
+
export interface SignupConsent {
|
|
37
|
+
policyType?: string; // e.g. 'default' | 'custom'
|
|
38
|
+
policyVersion: string;
|
|
39
|
+
policyRegion?: string; // ISO country code
|
|
40
|
+
policyLanguage: string;
|
|
41
|
+
region?: string;
|
|
42
|
+
consents: Record<string, boolean>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ── Phone / email OTP ──
|
|
46
|
+
// `contactType` is a frontend-only convenience field used to drive UI
|
|
47
|
+
// navigation (phone-confirm vs email-confirm). It is NOT sent to the
|
|
48
|
+
// backend — keel detects phone-vs-email from the `contact` value itself.
|
|
49
|
+
// `BaseAuthService.sendOtp` strips this field before posting, since keel
|
|
50
|
+
// rejects unknown JSON fields with 400.
|
|
51
|
+
export type OtpContactType = 'phone' | 'email';
|
|
52
|
+
export type OtpPurpose = 'login' | 'register' | 'verify';
|
|
53
|
+
|
|
54
|
+
export interface OtpRequest {
|
|
55
|
+
contact: string;
|
|
56
|
+
contactType?: OtpContactType; // frontend-only, stripped before POST
|
|
57
|
+
purpose?: OtpPurpose;
|
|
58
|
+
defaultRegion?: string; // ISO country hint for phone normalization
|
|
59
|
+
policyType?: string;
|
|
60
|
+
policyVersion?: string;
|
|
61
|
+
policyRegion?: string;
|
|
62
|
+
policyLanguage?: string;
|
|
63
|
+
region?: string;
|
|
64
|
+
consents?: Record<string, boolean>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// keel issues a 32-byte base64-URL opaque token bound to the user_id in
|
|
68
|
+
// the cache for ~5 minutes. Verify and Resend echo this back; an attacker
|
|
69
|
+
// guessing arbitrary user ids cannot reach Verify because the cache lookup
|
|
70
|
+
// fails. The login fall-through on unknown contacts also returns a token
|
|
71
|
+
// (resolving to user_id 0) so 200 responses don't leak which contacts are
|
|
72
|
+
// registered. Verify on a "fake" token returns the same generic 401 as a
|
|
73
|
+
// wrong code.
|
|
74
|
+
export interface OtpResponse {
|
|
75
|
+
otpToken: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface OtpVerifyRequest {
|
|
79
|
+
otpToken: string;
|
|
80
|
+
code: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface OtpVerifyResponse {
|
|
84
|
+
token: string;
|
|
85
|
+
userId?: number;
|
|
86
|
+
partnerId?: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface OtpResendRequest {
|
|
90
|
+
otpToken: string;
|
|
91
|
+
purpose?: OtpPurpose;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ── Re-authentication credentials ──
|
|
95
|
+
// Required by Setup2FA, Disable2FA, DeleteAccount, LogoutEverywhere — server
|
|
96
|
+
// rejects with 401 if neither `password` nor `twoFactorCode` is supplied.
|
|
97
|
+
// Disable2FA additionally requires the current TOTP `code` alongside password.
|
|
98
|
+
export interface ReauthCredentials {
|
|
99
|
+
password?: string;
|
|
100
|
+
twoFactorCode?: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ── Social login (ID token flow) ──
|
|
104
|
+
export type SocialProvider = 'google' | 'apple';
|
|
105
|
+
|
|
106
|
+
export interface SocialLoginRequest {
|
|
107
|
+
provider: SocialProvider;
|
|
108
|
+
token: string; // ID token from provider SDK
|
|
109
|
+
policyType?: string;
|
|
110
|
+
policyVersion?: string;
|
|
111
|
+
policyRegion?: string;
|
|
112
|
+
policyLanguage?: string;
|
|
113
|
+
region?: string;
|
|
114
|
+
consents?: Record<string, boolean>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface LoginResponseSocial {
|
|
118
|
+
token: string;
|
|
119
|
+
userId?: number;
|
|
120
|
+
partnerId?: number;
|
|
121
|
+
isNewUser: boolean;
|
|
122
|
+
singleDeviceSession?: boolean;
|
|
123
|
+
previousDeviceRevoked?: boolean;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── Push notification tokens ──
|
|
127
|
+
export type PushPlatform = 'I' | 'A' | 'W'; // iOS / Android / Web
|
|
128
|
+
|
|
129
|
+
export interface PushRegisterRequest {
|
|
130
|
+
platform: PushPlatform;
|
|
131
|
+
token: string;
|
|
132
|
+
appVersion?: string;
|
|
133
|
+
deviceModel?: string;
|
|
134
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { IsString, IsNumeric } from './decorator';
|
|
2
|
+
|
|
3
|
+
export class SiudAction {
|
|
4
|
+
op_code = 'S';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class UserAccountPolicy extends SiudAction {
|
|
8
|
+
@IsString(30)
|
|
9
|
+
Id?: string;
|
|
10
|
+
|
|
11
|
+
@IsNumeric(9, 0)
|
|
12
|
+
PolicyValue?: number;
|
|
13
|
+
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
export class ApplicationMenuItem extends SiudAction {
|
|
18
|
+
@IsString(30)
|
|
19
|
+
MenuId?: string;
|
|
20
|
+
|
|
21
|
+
@IsString(30)
|
|
22
|
+
ItemId?: string;
|
|
23
|
+
|
|
24
|
+
@IsString(80)
|
|
25
|
+
Caption?: string;
|
|
26
|
+
|
|
27
|
+
@IsNumeric(9, 0)
|
|
28
|
+
DisplayOrder?: number;
|
|
29
|
+
|
|
30
|
+
@IsString(255)
|
|
31
|
+
RestUri?: string;
|
|
32
|
+
|
|
33
|
+
@IsString(80)
|
|
34
|
+
Icon?: string;
|
|
35
|
+
|
|
36
|
+
IsActive?: boolean;
|
|
37
|
+
|
|
38
|
+
FilterOnList?: boolean;
|
|
39
|
+
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
export class ApplicationMenu extends SiudAction {
|
|
44
|
+
@IsString(30)
|
|
45
|
+
Id?: string;
|
|
46
|
+
|
|
47
|
+
@IsString(80)
|
|
48
|
+
Caption?: string;
|
|
49
|
+
|
|
50
|
+
@IsNumeric(9, 0)
|
|
51
|
+
DisplayOrder?: number;
|
|
52
|
+
|
|
53
|
+
@IsString(80)
|
|
54
|
+
Icon?: string;
|
|
55
|
+
|
|
56
|
+
IsActive?: boolean;
|
|
57
|
+
|
|
58
|
+
ApplicationMenuItems?: ApplicationMenuItem[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
export class AuthorizationRolePermission extends SiudAction {
|
|
63
|
+
@IsString(30)
|
|
64
|
+
RoleId?: string;
|
|
65
|
+
|
|
66
|
+
@IsString(30)
|
|
67
|
+
AuthorizationObjectId?: string;
|
|
68
|
+
|
|
69
|
+
@IsString(30)
|
|
70
|
+
Action?: string;
|
|
71
|
+
|
|
72
|
+
@IsString(80)
|
|
73
|
+
LowLimit?: string;
|
|
74
|
+
|
|
75
|
+
@IsString(80)
|
|
76
|
+
HighLimit?: string;
|
|
77
|
+
|
|
78
|
+
IsActive?: boolean;
|
|
79
|
+
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
export class ConstantValue extends SiudAction {
|
|
84
|
+
@IsString(60)
|
|
85
|
+
ConstantId?: string;
|
|
86
|
+
|
|
87
|
+
@IsString(80)
|
|
88
|
+
Value?: string;
|
|
89
|
+
|
|
90
|
+
@IsString(80)
|
|
91
|
+
Caption?: string;
|
|
92
|
+
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
export class UserAccount extends SiudAction {
|
|
97
|
+
@IsNumeric(9, 0)
|
|
98
|
+
Id?: number;
|
|
99
|
+
|
|
100
|
+
@IsString(80)
|
|
101
|
+
FirstName?: string;
|
|
102
|
+
|
|
103
|
+
@IsString(80)
|
|
104
|
+
LastName?: string;
|
|
105
|
+
|
|
106
|
+
@IsString(80)
|
|
107
|
+
UserName?: string;
|
|
108
|
+
|
|
109
|
+
@IsString(255)
|
|
110
|
+
UserEmail?: string;
|
|
111
|
+
|
|
112
|
+
@IsString(1)
|
|
113
|
+
Status?: string;
|
|
114
|
+
|
|
115
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ValidatorConstraint,
|
|
3
|
+
ValidatorConstraintInterface,
|
|
4
|
+
ValidationArguments,
|
|
5
|
+
} from 'class-validator';
|
|
6
|
+
|
|
7
|
+
@ValidatorConstraint({ name: 'isNumeric', async: false })
|
|
8
|
+
export class IsNumericConstraint implements ValidatorConstraintInterface {
|
|
9
|
+
validate(value: any, args: ValidationArguments) {
|
|
10
|
+
if (typeof value !== 'number') {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const [precision, scale] = args.constraints;
|
|
15
|
+
const valueString = value.toString();
|
|
16
|
+
const parts = valueString.split('.');
|
|
17
|
+
const integerPart = parts[0].startsWith('-') ? parts[0].substring(1) : parts[0];
|
|
18
|
+
const decimalPart = parts[1] || '';
|
|
19
|
+
|
|
20
|
+
// Check total digits vs precision and decimal digits vs scale
|
|
21
|
+
if (integerPart.length + decimalPart.length > precision || decimalPart.length > scale) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
defaultMessage(_args: ValidationArguments) {
|
|
29
|
+
return `\$property must be a numeric value with a total precision of \$constraint1 digits and maximum of \$constraint2 decimal places.`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@ValidatorConstraint({ name: 'IsString', async: false })
|
|
34
|
+
export class IsStringConstraint implements ValidatorConstraintInterface {
|
|
35
|
+
validate(value: any, args: ValidationArguments) {
|
|
36
|
+
const [maxLength] = args.constraints;
|
|
37
|
+
|
|
38
|
+
if (typeof value !== 'string') {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return value.length <= maxLength;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
defaultMessage(_args: ValidationArguments) {
|
|
46
|
+
return `\$property must be shorter than or equal to \$constraint1 characters.`;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
import { registerDecorator, ValidationOptions } from 'class-validator';
|
|
51
|
+
|
|
52
|
+
export function IsNumeric(precision: number, scale: number, validationOptions?: ValidationOptions) {
|
|
53
|
+
return function (object: Record<string, any>, propertyName: string) {
|
|
54
|
+
registerDecorator({
|
|
55
|
+
target: object.constructor,
|
|
56
|
+
propertyName: propertyName,
|
|
57
|
+
options: validationOptions,
|
|
58
|
+
constraints: [precision, scale],
|
|
59
|
+
validator: IsNumericConstraint,
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function IsString(maxLength: number, validationOptions?: ValidationOptions) {
|
|
65
|
+
return function (object: Record<string, any>, propertyName: string) {
|
|
66
|
+
registerDecorator({
|
|
67
|
+
target: object.constructor,
|
|
68
|
+
propertyName: propertyName,
|
|
69
|
+
options: validationOptions,
|
|
70
|
+
constraints: [maxLength],
|
|
71
|
+
validator: IsStringConstraint,
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { HttpErrorResponse, HttpInterceptorFn, HttpResponse } from '@angular/common/http';
|
|
2
|
+
import { catchError, map, throwError } from 'rxjs';
|
|
3
|
+
|
|
4
|
+
export const apiResponseInterceptor: HttpInterceptorFn = (req, next) => {
|
|
5
|
+
return next(req).pipe(
|
|
6
|
+
map(event => {
|
|
7
|
+
if (event instanceof HttpResponse) {
|
|
8
|
+
const body = event.body as Record<string, unknown> | null;
|
|
9
|
+
if (body && 'data' in body) {
|
|
10
|
+
return event.clone({ body: body['data'] });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return event;
|
|
14
|
+
}),
|
|
15
|
+
catchError((error: HttpErrorResponse) => {
|
|
16
|
+
const detail = error.error?.detail || error.error?.title || error.message;
|
|
17
|
+
return throwError(() => new HttpErrorResponse({
|
|
18
|
+
error: detail,
|
|
19
|
+
headers: error.headers,
|
|
20
|
+
status: error.status,
|
|
21
|
+
statusText: error.statusText,
|
|
22
|
+
url: error.url ?? undefined,
|
|
23
|
+
}));
|
|
24
|
+
}),
|
|
25
|
+
);
|
|
26
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { HttpInterceptorFn } from '@angular/common/http';
|
|
2
|
+
|
|
3
|
+
export const authInterceptor: HttpInterceptorFn = (req, next) => {
|
|
4
|
+
const token = localStorage.getItem('jwt');
|
|
5
|
+
if (token && req.url.includes('/api/')) {
|
|
6
|
+
req = req.clone({
|
|
7
|
+
setHeaders: { Authorization: `Bearer ${token}` },
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
return next(req);
|
|
11
|
+
};
|