@progalaxyelabs/ngx-stonescriptphp-client 1.10.0 → 1.12.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.
|
@@ -1,228 +1,34 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Injectable, Inject,
|
|
2
|
+
import { InjectionToken, makeEnvironmentProviders, Injectable, Inject, EventEmitter, Output, Input, Component, Optional } from '@angular/core';
|
|
3
3
|
import { BehaviorSubject } from 'rxjs';
|
|
4
4
|
import * as i3 from '@angular/common';
|
|
5
5
|
import { CommonModule } from '@angular/common';
|
|
6
6
|
import * as i4 from '@angular/forms';
|
|
7
7
|
import { FormsModule } from '@angular/forms';
|
|
8
8
|
|
|
9
|
-
class ApiResponse {
|
|
10
|
-
status;
|
|
11
|
-
data;
|
|
12
|
-
message;
|
|
13
|
-
get success() {
|
|
14
|
-
return this.status === 'ok';
|
|
15
|
-
}
|
|
16
|
-
get errors() {
|
|
17
|
-
return this.message ? [this.message] : [];
|
|
18
|
-
}
|
|
19
|
-
constructor(status, data = null, message = '') {
|
|
20
|
-
this.status = status;
|
|
21
|
-
this.data = data || null;
|
|
22
|
-
this.message = message;
|
|
23
|
-
}
|
|
24
|
-
onOk(callback) {
|
|
25
|
-
if (this.status === 'ok') {
|
|
26
|
-
callback(this.data);
|
|
27
|
-
}
|
|
28
|
-
return this;
|
|
29
|
-
}
|
|
30
|
-
onNotOk(callback) {
|
|
31
|
-
if (this.status === 'not ok') {
|
|
32
|
-
callback(this.message, this.data);
|
|
33
|
-
}
|
|
34
|
-
return this;
|
|
35
|
-
}
|
|
36
|
-
onError(callback) {
|
|
37
|
-
if (this.status === 'error') {
|
|
38
|
-
callback();
|
|
39
|
-
}
|
|
40
|
-
return this;
|
|
41
|
-
}
|
|
42
|
-
isSuccess() {
|
|
43
|
-
return this.status === 'ok';
|
|
44
|
-
}
|
|
45
|
-
isError() {
|
|
46
|
-
return this.status === 'error' || this.status === 'not ok';
|
|
47
|
-
}
|
|
48
|
-
getData() {
|
|
49
|
-
return this.data || null;
|
|
50
|
-
}
|
|
51
|
-
getError() {
|
|
52
|
-
return this.message || 'Unknown error';
|
|
53
|
-
}
|
|
54
|
-
getStatus() {
|
|
55
|
-
return this.status;
|
|
56
|
-
}
|
|
57
|
-
getMessage() {
|
|
58
|
-
return this.message;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
class TokenService {
|
|
63
|
-
accessToken = '';
|
|
64
|
-
refreshToken = '';
|
|
65
|
-
lsAccessTokenKey = 'progalaxyapi_access_token';
|
|
66
|
-
lsRefreshTokenKey = 'progalaxyapi_refresh_token';
|
|
67
|
-
constructor() { }
|
|
68
|
-
setTokens(accessToken, refreshToken) {
|
|
69
|
-
this.accessToken = accessToken;
|
|
70
|
-
this.refreshToken = refreshToken;
|
|
71
|
-
localStorage.setItem(this.lsAccessTokenKey, accessToken);
|
|
72
|
-
localStorage.setItem(this.lsRefreshTokenKey, refreshToken);
|
|
73
|
-
}
|
|
74
|
-
setAccessToken(accessToken) {
|
|
75
|
-
this.accessToken = accessToken;
|
|
76
|
-
localStorage.setItem(this.lsAccessTokenKey, accessToken);
|
|
77
|
-
}
|
|
78
|
-
setRefreshToken(refreshToken) {
|
|
79
|
-
this.refreshToken = refreshToken;
|
|
80
|
-
localStorage.setItem(this.lsRefreshTokenKey, refreshToken);
|
|
81
|
-
}
|
|
82
|
-
getAccessToken() {
|
|
83
|
-
if (this.accessToken) {
|
|
84
|
-
return this.accessToken;
|
|
85
|
-
}
|
|
86
|
-
const storedAccessToken = localStorage.getItem(this.lsAccessTokenKey);
|
|
87
|
-
if (storedAccessToken) {
|
|
88
|
-
return storedAccessToken;
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
return '';
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
getRefreshToken() {
|
|
95
|
-
if (this.refreshToken) {
|
|
96
|
-
return this.refreshToken;
|
|
97
|
-
}
|
|
98
|
-
const storedRefreshToken = localStorage.getItem(this.lsRefreshTokenKey);
|
|
99
|
-
if (storedRefreshToken) {
|
|
100
|
-
return storedRefreshToken;
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
return '';
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
clear() {
|
|
107
|
-
this.accessToken = '';
|
|
108
|
-
this.refreshToken = '';
|
|
109
|
-
localStorage.removeItem(this.lsAccessTokenKey);
|
|
110
|
-
localStorage.removeItem(this.lsRefreshTokenKey);
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Check if there is a valid (non-empty) access token
|
|
114
|
-
* @returns True if access token exists and is not empty
|
|
115
|
-
*/
|
|
116
|
-
hasValidAccessToken() {
|
|
117
|
-
const token = this.getAccessToken();
|
|
118
|
-
return token !== null && token !== '';
|
|
119
|
-
}
|
|
120
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TokenService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
121
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TokenService, providedIn: 'root' });
|
|
122
|
-
}
|
|
123
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TokenService, decorators: [{
|
|
124
|
-
type: Injectable,
|
|
125
|
-
args: [{
|
|
126
|
-
providedIn: 'root'
|
|
127
|
-
}]
|
|
128
|
-
}], ctorParameters: () => [] });
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* @deprecated Use boolean directly. Kept for backward compatibility.
|
|
132
|
-
*/
|
|
133
|
-
var VerifyStatus;
|
|
134
|
-
(function (VerifyStatus) {
|
|
135
|
-
VerifyStatus["initialized"] = "initialized";
|
|
136
|
-
VerifyStatus["yes"] = "yes";
|
|
137
|
-
VerifyStatus["no"] = "no";
|
|
138
|
-
})(VerifyStatus || (VerifyStatus = {}));
|
|
139
|
-
class SigninStatusService {
|
|
140
|
-
status;
|
|
141
|
-
constructor() {
|
|
142
|
-
this.status = new BehaviorSubject(false);
|
|
143
|
-
}
|
|
144
|
-
signedOut() {
|
|
145
|
-
this.status.next(false);
|
|
146
|
-
}
|
|
147
|
-
signedIn() {
|
|
148
|
-
this.status.next(true);
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* Set signin status
|
|
152
|
-
* @param isSignedIn - True if user is signed in, false otherwise
|
|
153
|
-
*/
|
|
154
|
-
setSigninStatus(isSignedIn) {
|
|
155
|
-
this.status.next(isSignedIn);
|
|
156
|
-
}
|
|
157
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SigninStatusService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
158
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SigninStatusService, providedIn: 'root' });
|
|
159
|
-
}
|
|
160
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SigninStatusService, decorators: [{
|
|
161
|
-
type: Injectable,
|
|
162
|
-
args: [{
|
|
163
|
-
providedIn: 'root'
|
|
164
|
-
}]
|
|
165
|
-
}], ctorParameters: () => [] });
|
|
166
|
-
|
|
167
9
|
class MyEnvironmentModel {
|
|
168
10
|
production = true;
|
|
169
11
|
/**
|
|
170
|
-
* Platform code identifier (e.g., 'progalaxy', 'hr', 'admin')
|
|
171
|
-
* Used for multi-tenant authentication
|
|
12
|
+
* Platform code identifier (e.g., 'progalaxy', 'hr', 'admin').
|
|
13
|
+
* Used for multi-tenant authentication.
|
|
172
14
|
*/
|
|
173
15
|
platformCode = '';
|
|
174
16
|
/**
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*/
|
|
179
|
-
accountsUrl = '';
|
|
180
|
-
/**
|
|
181
|
-
* Multiple authentication servers configuration
|
|
182
|
-
* Enables platforms to authenticate against different identity providers
|
|
183
|
-
* @example
|
|
184
|
-
* ```typescript
|
|
185
|
-
* authServers: {
|
|
186
|
-
* customer: { url: 'https://auth.progalaxyelabs.com', default: true },
|
|
187
|
-
* employee: { url: 'https://admin-auth.progalaxyelabs.com' }
|
|
188
|
-
* }
|
|
189
|
-
* ```
|
|
190
|
-
*/
|
|
191
|
-
authServers;
|
|
192
|
-
firebase = {
|
|
193
|
-
projectId: '',
|
|
194
|
-
appId: '',
|
|
195
|
-
databaseURL: '',
|
|
196
|
-
storageBucket: '',
|
|
197
|
-
locationId: '',
|
|
198
|
-
apiKey: '',
|
|
199
|
-
authDomain: '',
|
|
200
|
-
messagingSenderId: '',
|
|
201
|
-
measurementId: ''
|
|
202
|
-
};
|
|
203
|
-
/**
|
|
204
|
-
* Platform's own API base URL (e.g., '//api.medstoreapp.in')
|
|
205
|
-
* Used to route registration through the platform API proxy instead of auth directly
|
|
17
|
+
* Platform's own API base URL.
|
|
18
|
+
* Used for routes that go through the platform API proxy (e.g. register-tenant).
|
|
19
|
+
* Falls back to apiServer.host if not set.
|
|
206
20
|
* @example '//api.medstoreapp.in'
|
|
207
21
|
*/
|
|
208
22
|
apiUrl;
|
|
209
23
|
apiServer = { host: '' };
|
|
210
|
-
chatServer = { host: '' };
|
|
211
24
|
/**
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
* Replaces the deprecated accountsUrl string with structured config
|
|
215
|
-
* @example { host: 'https://accounts.progalaxyelabs.com' }
|
|
216
|
-
*/
|
|
217
|
-
accountsServer;
|
|
218
|
-
/**
|
|
219
|
-
* Files service server configuration
|
|
220
|
-
* Used by FilesService for file upload/download operations
|
|
25
|
+
* Files service server configuration.
|
|
26
|
+
* Used by FilesService for file upload/download operations.
|
|
221
27
|
* @example { host: 'https://files.progalaxyelabs.com/api/' }
|
|
222
28
|
*/
|
|
223
29
|
filesServer;
|
|
224
30
|
/**
|
|
225
|
-
* Authentication configuration
|
|
31
|
+
* Authentication configuration.
|
|
226
32
|
* @default { mode: 'cookie', refreshEndpoint: '/auth/refresh', useCsrf: true }
|
|
227
33
|
*/
|
|
228
34
|
auth = {
|
|
@@ -233,599 +39,816 @@ class MyEnvironmentModel {
|
|
|
233
39
|
csrfTokenCookieName: 'csrf_token',
|
|
234
40
|
csrfHeaderName: 'X-CSRF-Token'
|
|
235
41
|
};
|
|
42
|
+
/**
|
|
43
|
+
* Multiple authentication servers configuration (for StoneScriptPHPAuth multi-server mode).
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* authServers: {
|
|
47
|
+
* customer: { url: 'https://auth.progalaxyelabs.com', default: true },
|
|
48
|
+
* employee: { url: 'https://admin-auth.progalaxyelabs.com' }
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
authServers;
|
|
236
53
|
/**
|
|
237
54
|
* Custom OAuth provider configurations.
|
|
238
|
-
* Register additional OAuth providers beyond the built-in ones
|
|
239
|
-
* (google, linkedin, apple, microsoft, github, zoho).
|
|
240
55
|
* @example
|
|
241
56
|
* ```typescript
|
|
242
57
|
* customProviders: {
|
|
243
|
-
* okta: { label: 'Sign in with Okta', cssClass: 'btn-okta', buttonStyle: { borderColor: '#007dc1' } }
|
|
244
|
-
* keycloak: { label: 'Sign in with Keycloak', icon: '🔑' }
|
|
58
|
+
* okta: { label: 'Sign in with Okta', cssClass: 'btn-okta', buttonStyle: { borderColor: '#007dc1' } }
|
|
245
59
|
* }
|
|
246
60
|
* ```
|
|
247
61
|
*/
|
|
248
62
|
customProviders;
|
|
249
63
|
/**
|
|
250
|
-
* Branding configuration for auth components
|
|
251
|
-
* Allows platforms to customize login/register pages without creating wrappers
|
|
64
|
+
* Branding configuration for auth UI components.
|
|
252
65
|
*/
|
|
253
66
|
branding;
|
|
67
|
+
// ── Deprecated fields (kept for backward compatibility) ──────────────────
|
|
68
|
+
/**
|
|
69
|
+
* @deprecated Use auth.host instead.
|
|
70
|
+
* Auth server URL for centralized authentication.
|
|
71
|
+
*/
|
|
72
|
+
accountsUrl = '';
|
|
73
|
+
/**
|
|
74
|
+
* @deprecated Use auth.host instead.
|
|
75
|
+
* Accounts/Authentication service server configuration.
|
|
76
|
+
*/
|
|
77
|
+
accountsServer;
|
|
78
|
+
/**
|
|
79
|
+
* @deprecated Use auth.responseMap instead.
|
|
80
|
+
* Auth response field mapping for external auth compatibility.
|
|
81
|
+
*/
|
|
82
|
+
authResponseMap;
|
|
254
83
|
}
|
|
255
84
|
|
|
85
|
+
const AUTH_PLUGIN = new InjectionToken('AUTH_PLUGIN');
|
|
86
|
+
|
|
87
|
+
const ACTIVE_SERVER_KEY = 'progalaxyapi_active_auth_server';
|
|
88
|
+
const DEFAULT_RESPONSE_MAP = {
|
|
89
|
+
successPath: 'status',
|
|
90
|
+
successValue: 'ok',
|
|
91
|
+
accessTokenPath: 'data.access_token',
|
|
92
|
+
refreshTokenPath: 'data.refresh_token',
|
|
93
|
+
userPath: 'data.user',
|
|
94
|
+
errorMessagePath: 'message'
|
|
95
|
+
};
|
|
256
96
|
/**
|
|
257
|
-
*
|
|
97
|
+
* Built-in auth plugin for StoneScriptPHP backends.
|
|
258
98
|
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
99
|
+
* Handles StoneScriptPHP's auth format ({ status:'ok', data:{ access_token } })
|
|
100
|
+
* with optional authResponseMap overrides for external backends.
|
|
101
|
+
*
|
|
102
|
+
* Supports:
|
|
103
|
+
* - Cookie mode (httpOnly refresh token + CSRF)
|
|
104
|
+
* - Body mode (tokens in localStorage)
|
|
105
|
+
* - Multi-server auth (named authServers config)
|
|
106
|
+
* - OAuth popup login
|
|
107
|
+
* - Multi-tenant operations
|
|
261
108
|
*/
|
|
262
|
-
class
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
109
|
+
class StoneScriptPHPAuth {
|
|
110
|
+
config;
|
|
111
|
+
activeServer = null;
|
|
112
|
+
constructor(config) {
|
|
113
|
+
this.config = config;
|
|
114
|
+
this.restoreActiveServer();
|
|
115
|
+
}
|
|
116
|
+
// ── Response mapping ────────────────────────────────────────────────────
|
|
117
|
+
get responseMap() {
|
|
118
|
+
const m = this.config.responseMap;
|
|
119
|
+
if (!m)
|
|
120
|
+
return DEFAULT_RESPONSE_MAP;
|
|
121
|
+
return { ...DEFAULT_RESPONSE_MAP, ...m };
|
|
122
|
+
}
|
|
123
|
+
resolvePath(obj, path) {
|
|
124
|
+
return path.split('.').reduce((o, key) => o?.[key], obj);
|
|
125
|
+
}
|
|
126
|
+
isAuthSuccess(data) {
|
|
127
|
+
const map = this.responseMap;
|
|
128
|
+
if (map.successPath) {
|
|
129
|
+
return this.resolvePath(data, map.successPath) === (map.successValue ?? 'ok');
|
|
130
|
+
}
|
|
131
|
+
return !!this.resolvePath(data, map.accessTokenPath);
|
|
132
|
+
}
|
|
133
|
+
resolveAccessToken(data) {
|
|
134
|
+
return this.resolvePath(data, this.responseMap.accessTokenPath);
|
|
135
|
+
}
|
|
136
|
+
resolveRefreshToken(data) {
|
|
137
|
+
return this.resolvePath(data, this.responseMap.refreshTokenPath);
|
|
138
|
+
}
|
|
139
|
+
resolveUser(data) {
|
|
140
|
+
const raw = this.resolvePath(data, this.responseMap.userPath);
|
|
141
|
+
return raw ? this.normalizeUser(raw) : undefined;
|
|
142
|
+
}
|
|
143
|
+
resolveErrorMessage(data, fallback) {
|
|
144
|
+
const path = this.responseMap.errorMessagePath ?? 'message';
|
|
145
|
+
return this.resolvePath(data, path) || fallback;
|
|
146
|
+
}
|
|
147
|
+
normalizeUser(raw) {
|
|
148
|
+
return {
|
|
149
|
+
user_id: raw.user_id ?? (raw.id ? this.hashUUID(raw.id) : 0),
|
|
150
|
+
id: raw.id ?? String(raw.user_id),
|
|
151
|
+
email: raw.email,
|
|
152
|
+
display_name: raw.display_name ?? raw.email?.split('@')[0] ?? '',
|
|
153
|
+
photo_url: raw.photo_url,
|
|
154
|
+
is_email_verified: raw.is_email_verified ?? false
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
hashUUID(uuid) {
|
|
158
|
+
let hash = 0;
|
|
159
|
+
for (let i = 0; i < uuid.length; i++) {
|
|
160
|
+
const char = uuid.charCodeAt(i);
|
|
161
|
+
hash = ((hash << 5) - hash) + char;
|
|
162
|
+
hash = hash & hash;
|
|
163
|
+
}
|
|
164
|
+
return Math.abs(hash);
|
|
165
|
+
}
|
|
166
|
+
// ── CSRF (inlined, no Angular DI needed) ────────────────────────────────
|
|
167
|
+
getCsrfToken() {
|
|
168
|
+
const cookieName = this.config.auth?.csrfTokenCookieName ?? 'csrf_token';
|
|
267
169
|
const cookies = document.cookie.split(';');
|
|
268
170
|
for (const cookie of cookies) {
|
|
269
171
|
const [name, value] = cookie.trim().split('=');
|
|
270
|
-
if (name === cookieName)
|
|
172
|
+
if (name === cookieName)
|
|
271
173
|
return decodeURIComponent(value);
|
|
272
|
-
}
|
|
273
174
|
}
|
|
274
175
|
return null;
|
|
275
176
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
177
|
+
// ── Server resolution ────────────────────────────────────────────────────
|
|
178
|
+
getAccountsUrl(serverName) {
|
|
179
|
+
if (this.config.authServers && Object.keys(this.config.authServers).length > 0) {
|
|
180
|
+
const target = serverName || this.activeServer || this.getDefaultServer();
|
|
181
|
+
if (!target)
|
|
182
|
+
throw new Error('No auth server specified and no default server configured');
|
|
183
|
+
const serverConfig = this.config.authServers[target];
|
|
184
|
+
if (!serverConfig)
|
|
185
|
+
throw new Error(`Auth server '${target}' not found in configuration`);
|
|
186
|
+
return serverConfig.url;
|
|
187
|
+
}
|
|
188
|
+
return this.config.host;
|
|
281
189
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
* Note: Client-side deletion is limited for httpOnly cookies
|
|
285
|
-
*/
|
|
286
|
-
clearCsrfToken(cookieName = 'csrf_token') {
|
|
287
|
-
// Can only clear non-httpOnly cookies
|
|
288
|
-
document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
|
|
190
|
+
getPlatformApiUrl() {
|
|
191
|
+
return this.config.apiUrl || this.config.host;
|
|
289
192
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
providedIn: 'root'
|
|
297
|
-
}]
|
|
298
|
-
}] });
|
|
299
|
-
|
|
300
|
-
class ApiConnectionService {
|
|
301
|
-
tokens;
|
|
302
|
-
signinStatus;
|
|
303
|
-
environment;
|
|
304
|
-
csrf;
|
|
305
|
-
host = ''; // base URL without trailing slash
|
|
306
|
-
accessToken = '';
|
|
307
|
-
authConfig;
|
|
308
|
-
constructor(tokens, signinStatus, environment, csrf) {
|
|
309
|
-
this.tokens = tokens;
|
|
310
|
-
this.signinStatus = signinStatus;
|
|
311
|
-
this.environment = environment;
|
|
312
|
-
this.csrf = csrf;
|
|
313
|
-
this.host = environment.apiServer.host;
|
|
314
|
-
// Set default auth config based on mode
|
|
315
|
-
this.authConfig = {
|
|
316
|
-
mode: environment.auth?.mode || 'cookie',
|
|
317
|
-
refreshEndpoint: environment.auth?.refreshEndpoint,
|
|
318
|
-
useCsrf: environment.auth?.useCsrf,
|
|
319
|
-
refreshTokenCookieName: environment.auth?.refreshTokenCookieName || 'refresh_token',
|
|
320
|
-
csrfTokenCookieName: environment.auth?.csrfTokenCookieName || 'csrf_token',
|
|
321
|
-
csrfHeaderName: environment.auth?.csrfHeaderName || 'X-CSRF-Token'
|
|
322
|
-
};
|
|
323
|
-
// Set default refresh endpoint based on mode if not specified
|
|
324
|
-
if (!this.authConfig.refreshEndpoint) {
|
|
325
|
-
this.authConfig.refreshEndpoint = this.authConfig.mode === 'cookie'
|
|
326
|
-
? '/auth/refresh'
|
|
327
|
-
: '/user/refresh_access';
|
|
328
|
-
}
|
|
329
|
-
// Set default CSRF setting based on mode if not specified
|
|
330
|
-
if (this.authConfig.useCsrf === undefined) {
|
|
331
|
-
this.authConfig.useCsrf = this.authConfig.mode === 'cookie';
|
|
193
|
+
getDefaultServer() {
|
|
194
|
+
if (!this.config.authServers)
|
|
195
|
+
return null;
|
|
196
|
+
for (const [name, cfg] of Object.entries(this.config.authServers)) {
|
|
197
|
+
if (cfg.default)
|
|
198
|
+
return name;
|
|
332
199
|
}
|
|
200
|
+
return Object.keys(this.config.authServers)[0] || null;
|
|
333
201
|
}
|
|
334
|
-
|
|
202
|
+
restoreActiveServer() {
|
|
335
203
|
try {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
else {
|
|
342
|
-
options.body = {};
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
const accessTokenIncluded = this.includeAccessToken(options);
|
|
346
|
-
let response = await fetch(url, options);
|
|
347
|
-
if ((response.status === 401) && accessTokenIncluded) {
|
|
348
|
-
response = await this.refreshAccessTokenAndRetry(url, options, response);
|
|
349
|
-
}
|
|
350
|
-
if (response.ok) {
|
|
351
|
-
const json = await (response.json());
|
|
352
|
-
return (new ApiResponse(json.status, json.data, json.message));
|
|
353
|
-
}
|
|
354
|
-
if (response.status === 401) {
|
|
355
|
-
this.signinStatus.signedOut();
|
|
356
|
-
}
|
|
357
|
-
return this.handleError(response);
|
|
204
|
+
const saved = localStorage.getItem(ACTIVE_SERVER_KEY);
|
|
205
|
+
this.activeServer = (saved && this.config.authServers?.[saved])
|
|
206
|
+
? saved
|
|
207
|
+
: this.getDefaultServer();
|
|
358
208
|
}
|
|
359
|
-
catch
|
|
360
|
-
|
|
209
|
+
catch {
|
|
210
|
+
this.activeServer = this.getDefaultServer();
|
|
361
211
|
}
|
|
362
212
|
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
};
|
|
374
|
-
return this.request(url, fetchOptions, null);
|
|
375
|
-
}
|
|
376
|
-
async post(pathWithQueryParams, data) {
|
|
377
|
-
const url = this.host + pathWithQueryParams;
|
|
378
|
-
const fetchOptions = {
|
|
379
|
-
method: 'POST',
|
|
380
|
-
mode: 'cors',
|
|
381
|
-
redirect: 'error',
|
|
382
|
-
headers: {
|
|
383
|
-
'Content-Type': 'application/json'
|
|
384
|
-
},
|
|
385
|
-
body: JSON.stringify(data)
|
|
386
|
-
};
|
|
387
|
-
return this.request(url, fetchOptions, data);
|
|
213
|
+
// ── Multi-server public API ──────────────────────────────────────────────
|
|
214
|
+
switchServer(serverName) {
|
|
215
|
+
if (!this.config.authServers?.[serverName]) {
|
|
216
|
+
throw new Error(`Auth server '${serverName}' not found in configuration`);
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
localStorage.setItem(ACTIVE_SERVER_KEY, serverName);
|
|
220
|
+
}
|
|
221
|
+
catch { /* ignore */ }
|
|
222
|
+
this.activeServer = serverName;
|
|
388
223
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
const fetchOptions = {
|
|
392
|
-
method: 'PUT',
|
|
393
|
-
mode: 'cors',
|
|
394
|
-
redirect: 'error',
|
|
395
|
-
headers: {
|
|
396
|
-
'Content-Type': 'application/json'
|
|
397
|
-
},
|
|
398
|
-
body: JSON.stringify(data)
|
|
399
|
-
};
|
|
400
|
-
return this.request(url, fetchOptions, data);
|
|
224
|
+
getAvailableServers() {
|
|
225
|
+
return Object.keys(this.config.authServers ?? {});
|
|
401
226
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
const fetchOptions = {
|
|
405
|
-
method: 'PATCH',
|
|
406
|
-
mode: 'cors',
|
|
407
|
-
redirect: 'error',
|
|
408
|
-
headers: {
|
|
409
|
-
'Content-Type': 'application/json'
|
|
410
|
-
},
|
|
411
|
-
body: JSON.stringify(data)
|
|
412
|
-
};
|
|
413
|
-
return this.request(url, fetchOptions, data);
|
|
227
|
+
getActiveServer() {
|
|
228
|
+
return this.activeServer;
|
|
414
229
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
redirect: 'error'
|
|
421
|
-
};
|
|
422
|
-
return this.request(url, fetchOptions, null);
|
|
230
|
+
getServerConfig(serverName) {
|
|
231
|
+
if (!this.config.authServers)
|
|
232
|
+
return null;
|
|
233
|
+
const target = serverName || this.activeServer || this.getDefaultServer();
|
|
234
|
+
return target ? (this.config.authServers[target] ?? null) : null;
|
|
423
235
|
}
|
|
424
|
-
//
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
// }
|
|
445
|
-
// }
|
|
446
|
-
includeAccessToken(options) {
|
|
447
|
-
this.accessToken = this.tokens.getAccessToken();
|
|
448
|
-
if (!this.accessToken) {
|
|
449
|
-
return false;
|
|
236
|
+
// ── Core auth operations ─────────────────────────────────────────────────
|
|
237
|
+
async login(email, password) {
|
|
238
|
+
try {
|
|
239
|
+
const accountsUrl = this.getAccountsUrl();
|
|
240
|
+
const response = await fetch(`${accountsUrl}/api/auth/login`, {
|
|
241
|
+
method: 'POST',
|
|
242
|
+
headers: { 'Content-Type': 'application/json' },
|
|
243
|
+
credentials: 'include',
|
|
244
|
+
body: JSON.stringify({ email, password, platform: this.config.platformCode })
|
|
245
|
+
});
|
|
246
|
+
const data = await response.json();
|
|
247
|
+
if (this.isAuthSuccess(data)) {
|
|
248
|
+
return {
|
|
249
|
+
success: true,
|
|
250
|
+
accessToken: this.resolveAccessToken(data),
|
|
251
|
+
refreshToken: this.resolveRefreshToken(data),
|
|
252
|
+
user: this.resolveUser(data)
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return { success: false, message: this.resolveErrorMessage(data, 'Invalid credentials') };
|
|
450
256
|
}
|
|
451
|
-
|
|
452
|
-
|
|
257
|
+
catch {
|
|
258
|
+
return { success: false, message: 'Network error. Please try again.' };
|
|
453
259
|
}
|
|
454
|
-
options.headers['Authorization'] = 'Bearer ' + this.accessToken;
|
|
455
|
-
return true;
|
|
456
260
|
}
|
|
457
|
-
async
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
261
|
+
async register(email, password, displayName) {
|
|
262
|
+
try {
|
|
263
|
+
const accountsUrl = this.getAccountsUrl();
|
|
264
|
+
const response = await fetch(`${accountsUrl}/api/auth/register`, {
|
|
265
|
+
method: 'POST',
|
|
266
|
+
headers: { 'Content-Type': 'application/json' },
|
|
267
|
+
credentials: 'include',
|
|
268
|
+
body: JSON.stringify({
|
|
269
|
+
email,
|
|
270
|
+
password,
|
|
271
|
+
display_name: displayName,
|
|
272
|
+
platform: this.config.platformCode
|
|
273
|
+
})
|
|
274
|
+
});
|
|
275
|
+
const data = await response.json();
|
|
276
|
+
if (this.isAuthSuccess(data)) {
|
|
277
|
+
return {
|
|
278
|
+
success: true,
|
|
279
|
+
accessToken: this.resolveAccessToken(data),
|
|
280
|
+
refreshToken: this.resolveRefreshToken(data),
|
|
281
|
+
user: this.resolveUser(data),
|
|
282
|
+
needsVerification: !!data.needs_verification,
|
|
283
|
+
message: data.needs_verification ? 'Please verify your email' : undefined
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
return { success: false, message: this.resolveErrorMessage(data, 'Registration failed') };
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
return { success: false, message: 'Network error. Please try again.' };
|
|
461
290
|
}
|
|
462
|
-
fetchOptions.headers['Authorization'] = 'Bearer ' + this.accessToken;
|
|
463
|
-
response = await fetch(url, fetchOptions);
|
|
464
|
-
return response;
|
|
465
291
|
}
|
|
466
|
-
async
|
|
467
|
-
|
|
468
|
-
|
|
292
|
+
async logout(refreshToken) {
|
|
293
|
+
try {
|
|
294
|
+
const accountsUrl = this.getAccountsUrl();
|
|
295
|
+
await fetch(`${accountsUrl}/api/auth/logout`, {
|
|
296
|
+
method: 'POST',
|
|
297
|
+
headers: { 'Content-Type': 'application/json' },
|
|
298
|
+
credentials: 'include',
|
|
299
|
+
body: JSON.stringify({ refresh_token: refreshToken })
|
|
300
|
+
});
|
|
469
301
|
}
|
|
470
|
-
|
|
471
|
-
|
|
302
|
+
catch (error) {
|
|
303
|
+
console.error('Logout API call failed:', error);
|
|
472
304
|
}
|
|
473
|
-
|
|
474
|
-
|
|
305
|
+
}
|
|
306
|
+
async checkSession() {
|
|
307
|
+
try {
|
|
308
|
+
const accountsUrl = this.getAccountsUrl();
|
|
309
|
+
const response = await fetch(`${accountsUrl}/api/auth/refresh`, {
|
|
310
|
+
method: 'POST',
|
|
311
|
+
credentials: 'include'
|
|
312
|
+
});
|
|
313
|
+
if (!response.ok)
|
|
314
|
+
return { success: false };
|
|
315
|
+
const data = await response.json();
|
|
316
|
+
const accessToken = this.resolveAccessToken(data);
|
|
317
|
+
if (accessToken) {
|
|
318
|
+
return { success: true, accessToken, user: this.resolveUser(data) };
|
|
319
|
+
}
|
|
320
|
+
return { success: false };
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
return { success: false };
|
|
475
324
|
}
|
|
476
325
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
326
|
+
async refresh(accessToken, refreshToken) {
|
|
327
|
+
const mode = this.config.auth?.mode ?? 'cookie';
|
|
328
|
+
if (mode === 'none')
|
|
329
|
+
return null;
|
|
330
|
+
return mode === 'cookie'
|
|
331
|
+
? this.refreshCookieMode()
|
|
332
|
+
: this.refreshBodyMode(accessToken, refreshToken);
|
|
333
|
+
}
|
|
334
|
+
async refreshCookieMode() {
|
|
481
335
|
try {
|
|
482
|
-
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
const headers = {
|
|
488
|
-
'Content-Type': 'application/json'
|
|
489
|
-
};
|
|
490
|
-
// Add CSRF token if enabled
|
|
491
|
-
if (this.authConfig.useCsrf) {
|
|
492
|
-
const csrfToken = this.csrf.getCsrfToken(this.authConfig.csrfTokenCookieName);
|
|
336
|
+
const authHost = this.getAccountsUrl();
|
|
337
|
+
const endpoint = this.config.auth?.refreshEndpoint ?? '/auth/refresh';
|
|
338
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
339
|
+
if (this.config.auth?.useCsrf !== false) {
|
|
340
|
+
const csrfToken = this.getCsrfToken();
|
|
493
341
|
if (!csrfToken) {
|
|
494
342
|
console.error('CSRF token not found in cookie');
|
|
495
|
-
return
|
|
343
|
+
return null;
|
|
496
344
|
}
|
|
497
|
-
headers[this.
|
|
345
|
+
headers[this.config.auth?.csrfHeaderName ?? 'X-CSRF-Token'] = csrfToken;
|
|
498
346
|
}
|
|
499
|
-
|
|
347
|
+
const response = await fetch(`${authHost}${endpoint}`, {
|
|
500
348
|
method: 'POST',
|
|
501
349
|
mode: 'cors',
|
|
502
|
-
credentials: 'include',
|
|
350
|
+
credentials: 'include',
|
|
503
351
|
redirect: 'error',
|
|
504
|
-
headers
|
|
352
|
+
headers
|
|
505
353
|
});
|
|
506
|
-
if (!
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
if (!refreshAccessData || refreshAccessData.status !== 'ok') {
|
|
513
|
-
return false;
|
|
514
|
-
}
|
|
515
|
-
// Extract access token from response
|
|
516
|
-
const newAccessToken = refreshAccessData.data?.access_token || refreshAccessData.access_token;
|
|
517
|
-
if (!newAccessToken) {
|
|
518
|
-
console.error('No access token in refresh response');
|
|
519
|
-
return false;
|
|
520
|
-
}
|
|
521
|
-
// Store new access token (refresh token is in httpOnly cookie)
|
|
522
|
-
this.tokens.setAccessToken(newAccessToken);
|
|
523
|
-
this.accessToken = newAccessToken;
|
|
524
|
-
return true;
|
|
354
|
+
if (!response.ok)
|
|
355
|
+
return null;
|
|
356
|
+
const data = await response.json();
|
|
357
|
+
if (!this.isAuthSuccess(data))
|
|
358
|
+
return null;
|
|
359
|
+
return this.resolveAccessToken(data) ?? null;
|
|
525
360
|
}
|
|
526
361
|
catch (error) {
|
|
527
362
|
console.error('Token refresh failed (cookie mode):', error);
|
|
528
|
-
|
|
529
|
-
this.tokens.clear();
|
|
530
|
-
return false;
|
|
363
|
+
return null;
|
|
531
364
|
}
|
|
532
365
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
async refreshAccessTokenBodyMode() {
|
|
366
|
+
async refreshBodyMode(accessToken, refreshToken) {
|
|
367
|
+
if (!refreshToken)
|
|
368
|
+
return null;
|
|
537
369
|
try {
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
}
|
|
542
|
-
// Determine auth server: accountsServer.host > accountsUrl > apiServer.host
|
|
543
|
-
const authHost = this.environment.accountsServer?.host
|
|
544
|
-
|| this.environment.accountsUrl
|
|
545
|
-
|| this.host;
|
|
546
|
-
const refreshTokenUrl = authHost + this.authConfig.refreshEndpoint;
|
|
547
|
-
let refreshTokenResponse = await fetch(refreshTokenUrl, {
|
|
370
|
+
const authHost = this.getAccountsUrl();
|
|
371
|
+
const endpoint = this.config.auth?.refreshEndpoint ?? '/user/refresh_access';
|
|
372
|
+
const response = await fetch(`${authHost}${endpoint}`, {
|
|
548
373
|
method: 'POST',
|
|
549
374
|
mode: 'cors',
|
|
550
375
|
redirect: 'error',
|
|
551
|
-
headers: {
|
|
552
|
-
|
|
553
|
-
},
|
|
554
|
-
body: JSON.stringify({
|
|
555
|
-
access_token: this.accessToken,
|
|
556
|
-
refresh_token: refreshToken
|
|
557
|
-
})
|
|
376
|
+
headers: { 'Content-Type': 'application/json' },
|
|
377
|
+
body: JSON.stringify({ access_token: accessToken, refresh_token: refreshToken })
|
|
558
378
|
});
|
|
559
|
-
if (!
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
}
|
|
564
|
-
let refreshAccessData = await refreshTokenResponse.json();
|
|
565
|
-
if (!refreshAccessData) {
|
|
566
|
-
return false;
|
|
567
|
-
}
|
|
568
|
-
const newAccessToken = refreshAccessData.data?.access_token || refreshAccessData.access_token;
|
|
569
|
-
if (!newAccessToken) {
|
|
570
|
-
console.error('No access token in refresh response');
|
|
571
|
-
return false;
|
|
572
|
-
}
|
|
573
|
-
this.tokens.setTokens(newAccessToken, refreshToken);
|
|
574
|
-
this.accessToken = newAccessToken;
|
|
575
|
-
return true;
|
|
379
|
+
if (!response.ok)
|
|
380
|
+
return null;
|
|
381
|
+
const data = await response.json();
|
|
382
|
+
return this.resolveAccessToken(data) ?? null;
|
|
576
383
|
}
|
|
577
384
|
catch (error) {
|
|
578
385
|
console.error('Token refresh failed (body mode):', error);
|
|
579
|
-
|
|
580
|
-
this.tokens.clear();
|
|
581
|
-
return false;
|
|
386
|
+
return null;
|
|
582
387
|
}
|
|
583
388
|
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
389
|
+
// ── OAuth ────────────────────────────────────────────────────────────────
|
|
390
|
+
async loginWithProvider(provider) {
|
|
391
|
+
return new Promise((resolve) => {
|
|
392
|
+
const width = 500, height = 600;
|
|
393
|
+
const left = (window.screen.width - width) / 2;
|
|
394
|
+
const top = (window.screen.height - height) / 2;
|
|
395
|
+
const accountsUrl = this.getAccountsUrl();
|
|
396
|
+
const oauthUrl = `${accountsUrl}/oauth/${provider}?platform=${this.config.platformCode}&mode=popup`;
|
|
397
|
+
const popup = window.open(oauthUrl, `${provider}_login`, `width=${width},height=${height},left=${left},top=${top}`);
|
|
398
|
+
if (!popup) {
|
|
399
|
+
resolve({ success: false, message: 'Popup blocked. Please allow popups for this site.' });
|
|
400
|
+
return;
|
|
592
401
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
402
|
+
const messageHandler = (event) => {
|
|
403
|
+
if (event.origin !== new URL(accountsUrl).origin)
|
|
404
|
+
return;
|
|
405
|
+
if (event.data.type === 'oauth_success') {
|
|
406
|
+
window.removeEventListener('message', messageHandler);
|
|
407
|
+
popup.close();
|
|
408
|
+
const rawUser = event.data.user || this.resolveUser(event.data);
|
|
409
|
+
resolve({
|
|
410
|
+
success: true,
|
|
411
|
+
accessToken: event.data.access_token,
|
|
412
|
+
user: rawUser ? this.normalizeUser(rawUser) : undefined
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
else if (event.data.type === 'oauth_error') {
|
|
416
|
+
window.removeEventListener('message', messageHandler);
|
|
417
|
+
popup.close();
|
|
418
|
+
resolve({ success: false, message: event.data.message || 'OAuth login failed' });
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
window.addEventListener('message', messageHandler);
|
|
422
|
+
const checkClosed = setInterval(() => {
|
|
423
|
+
if (popup.closed) {
|
|
424
|
+
clearInterval(checkClosed);
|
|
425
|
+
window.removeEventListener('message', messageHandler);
|
|
426
|
+
resolve({ success: false, message: 'Login cancelled' });
|
|
427
|
+
}
|
|
428
|
+
}, 500);
|
|
429
|
+
});
|
|
599
430
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
* Upload an image (uses upload server if configured, otherwise API server)
|
|
617
|
-
* @deprecated Platform-specific method - consider moving to platform service
|
|
618
|
-
*/
|
|
619
|
-
async uploadImage(formData) {
|
|
620
|
-
const uploadHost = this.environment.uploadServer?.host || this.host;
|
|
621
|
-
const url = uploadHost + '/upload/image';
|
|
622
|
-
const fetchOptions = {
|
|
623
|
-
method: 'POST',
|
|
624
|
-
mode: 'cors',
|
|
625
|
-
redirect: 'error',
|
|
626
|
-
body: formData
|
|
627
|
-
};
|
|
628
|
-
return this.request(url, fetchOptions, null);
|
|
629
|
-
}
|
|
630
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ApiConnectionService, deps: [{ token: TokenService }, { token: SigninStatusService }, { token: MyEnvironmentModel }, { token: CsrfService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
631
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ApiConnectionService, providedIn: 'root' });
|
|
632
|
-
}
|
|
633
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ApiConnectionService, decorators: [{
|
|
634
|
-
type: Injectable,
|
|
635
|
-
args: [{
|
|
636
|
-
providedIn: 'root'
|
|
637
|
-
}]
|
|
638
|
-
}], ctorParameters: () => [{ type: TokenService }, { type: SigninStatusService }, { type: MyEnvironmentModel }, { type: CsrfService }] });
|
|
639
|
-
|
|
640
|
-
class AuthService {
|
|
641
|
-
tokens;
|
|
642
|
-
signinStatus;
|
|
643
|
-
environment;
|
|
644
|
-
USER_STORAGE_KEY = 'progalaxyapi_user';
|
|
645
|
-
ACTIVE_AUTH_SERVER_KEY = 'progalaxyapi_active_auth_server';
|
|
646
|
-
// Observable user state
|
|
647
|
-
userSubject = new BehaviorSubject(null);
|
|
648
|
-
user$ = this.userSubject.asObservable();
|
|
649
|
-
// Current active auth server name (for multi-server mode)
|
|
650
|
-
activeAuthServer = null;
|
|
651
|
-
constructor(tokens, signinStatus, environment) {
|
|
652
|
-
this.tokens = tokens;
|
|
653
|
-
this.signinStatus = signinStatus;
|
|
654
|
-
this.environment = environment;
|
|
655
|
-
// Restore user from localStorage on initialization
|
|
656
|
-
this.restoreUser();
|
|
657
|
-
// Restore active auth server
|
|
658
|
-
this.restoreActiveAuthServer();
|
|
659
|
-
}
|
|
660
|
-
// ===== Multi-Server Support Methods =====
|
|
661
|
-
/**
|
|
662
|
-
* Get the current accounts URL based on configuration
|
|
663
|
-
* Supports both single-server (accountsUrl) and multi-server (authServers) modes
|
|
664
|
-
* @param serverName - Optional server name for multi-server mode
|
|
665
|
-
*/
|
|
666
|
-
getAccountsUrl(serverName) {
|
|
667
|
-
// Multi-server mode
|
|
668
|
-
if (this.environment.authServers && Object.keys(this.environment.authServers).length > 0) {
|
|
669
|
-
const targetServer = serverName || this.activeAuthServer || this.getDefaultAuthServer();
|
|
670
|
-
if (!targetServer) {
|
|
671
|
-
throw new Error('No auth server specified and no default server configured');
|
|
672
|
-
}
|
|
673
|
-
const serverConfig = this.environment.authServers[targetServer];
|
|
674
|
-
if (!serverConfig) {
|
|
675
|
-
throw new Error(`Auth server '${targetServer}' not found in configuration`);
|
|
431
|
+
// ── Multi-tenant ─────────────────────────────────────────────────────────
|
|
432
|
+
async selectTenant(tenantId, accessToken) {
|
|
433
|
+
try {
|
|
434
|
+
const accountsUrl = this.getAccountsUrl();
|
|
435
|
+
const response = await fetch(`${accountsUrl}/api/auth/select-tenant`, {
|
|
436
|
+
method: 'POST',
|
|
437
|
+
headers: {
|
|
438
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
439
|
+
'Content-Type': 'application/json'
|
|
440
|
+
},
|
|
441
|
+
credentials: 'include',
|
|
442
|
+
body: JSON.stringify({ tenant_id: tenantId })
|
|
443
|
+
});
|
|
444
|
+
const data = await response.json();
|
|
445
|
+
if (this.isAuthSuccess(data)) {
|
|
446
|
+
return { success: true, accessToken: this.resolveAccessToken(data) };
|
|
676
447
|
}
|
|
677
|
-
return
|
|
448
|
+
return { success: false, message: this.resolveErrorMessage(data, 'Failed to select tenant') };
|
|
678
449
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
return this.environment.accountsUrl;
|
|
450
|
+
catch {
|
|
451
|
+
return { success: false, message: 'Network error. Please try again.' };
|
|
682
452
|
}
|
|
683
|
-
throw new Error('No authentication server configured. Set either accountsUrl or authServers in environment config.');
|
|
684
453
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
454
|
+
async getTenantMemberships(accessToken) {
|
|
455
|
+
try {
|
|
456
|
+
const accountsUrl = this.getAccountsUrl();
|
|
457
|
+
const response = await fetch(`${accountsUrl}/api/auth/memberships`, {
|
|
458
|
+
method: 'GET',
|
|
459
|
+
headers: {
|
|
460
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
461
|
+
'Content-Type': 'application/json'
|
|
462
|
+
},
|
|
463
|
+
credentials: 'include'
|
|
464
|
+
});
|
|
465
|
+
const data = await response.json();
|
|
466
|
+
return data.memberships || [];
|
|
691
467
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
if (config.default) {
|
|
695
|
-
return name;
|
|
696
|
-
}
|
|
468
|
+
catch {
|
|
469
|
+
return [];
|
|
697
470
|
}
|
|
698
|
-
// If no default is marked, use the first server
|
|
699
|
-
const firstServer = Object.keys(this.environment.authServers)[0];
|
|
700
|
-
return firstServer || null;
|
|
701
471
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
472
|
+
async registerTenant(data, accessToken) {
|
|
473
|
+
if (data.provider !== 'emailPassword') {
|
|
474
|
+
return this.registerTenantWithOAuth(data.tenantName, data.provider);
|
|
475
|
+
}
|
|
706
476
|
try {
|
|
707
|
-
const
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
477
|
+
const apiUrl = this.getPlatformApiUrl();
|
|
478
|
+
const body = {
|
|
479
|
+
tenant_name: data.tenantName,
|
|
480
|
+
email: data.email ?? '',
|
|
481
|
+
password: data.password ?? '',
|
|
482
|
+
provider: 'emailPassword'
|
|
483
|
+
};
|
|
484
|
+
if (data.displayName)
|
|
485
|
+
body['display_name'] = data.displayName;
|
|
486
|
+
if (data.countryCode)
|
|
487
|
+
body['country_code'] = data.countryCode;
|
|
488
|
+
if (data.role)
|
|
489
|
+
body['role'] = data.role;
|
|
490
|
+
const response = await fetch(`${apiUrl}/auth/register-tenant`, {
|
|
491
|
+
method: 'POST',
|
|
492
|
+
headers: {
|
|
493
|
+
'Content-Type': 'application/json',
|
|
494
|
+
...(accessToken ? { 'Authorization': `Bearer ${accessToken}` } : {})
|
|
495
|
+
},
|
|
496
|
+
credentials: 'include',
|
|
497
|
+
body: JSON.stringify(body)
|
|
498
|
+
});
|
|
499
|
+
return response.json();
|
|
500
|
+
}
|
|
501
|
+
catch {
|
|
502
|
+
return { success: false, message: 'Network error. Please try again.' };
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
async registerTenantWithOAuth(tenantName, provider) {
|
|
506
|
+
return new Promise((resolve) => {
|
|
507
|
+
const width = 500, height = 600;
|
|
508
|
+
const left = (window.screen.width - width) / 2;
|
|
509
|
+
const top = (window.screen.height - height) / 2;
|
|
510
|
+
const accountsUrl = this.getAccountsUrl();
|
|
511
|
+
const oauthUrl = `${accountsUrl}/oauth/${provider}?` +
|
|
512
|
+
`platform=${this.config.platformCode}&mode=popup&action=register_tenant&` +
|
|
513
|
+
`tenant_name=${encodeURIComponent(tenantName)}`;
|
|
514
|
+
const popup = window.open(oauthUrl, `${provider}_register_tenant`, `width=${width},height=${height},left=${left},top=${top}`);
|
|
515
|
+
if (!popup) {
|
|
516
|
+
resolve({ success: false, message: 'Popup blocked. Please allow popups for this site.' });
|
|
517
|
+
return;
|
|
714
518
|
}
|
|
519
|
+
const messageHandler = (event) => {
|
|
520
|
+
if (event.origin !== new URL(accountsUrl).origin)
|
|
521
|
+
return;
|
|
522
|
+
if (event.data.type === 'tenant_register_success') {
|
|
523
|
+
window.removeEventListener('message', messageHandler);
|
|
524
|
+
popup.close();
|
|
525
|
+
resolve({ success: true, tenant: event.data.tenant, user: event.data.user,
|
|
526
|
+
access_token: event.data.access_token });
|
|
527
|
+
}
|
|
528
|
+
else if (event.data.type === 'tenant_register_error') {
|
|
529
|
+
window.removeEventListener('message', messageHandler);
|
|
530
|
+
popup.close();
|
|
531
|
+
resolve({ success: false, message: event.data.message || 'Tenant registration failed' });
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
window.addEventListener('message', messageHandler);
|
|
535
|
+
const checkClosed = setInterval(() => {
|
|
536
|
+
if (popup.closed) {
|
|
537
|
+
clearInterval(checkClosed);
|
|
538
|
+
window.removeEventListener('message', messageHandler);
|
|
539
|
+
resolve({ success: false, message: 'Registration cancelled' });
|
|
540
|
+
}
|
|
541
|
+
}, 500);
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
async checkTenantSlugAvailable(slug) {
|
|
545
|
+
try {
|
|
546
|
+
const accountsUrl = this.getAccountsUrl();
|
|
547
|
+
const response = await fetch(`${accountsUrl}/api/auth/check-tenant-slug/${slug}`, {
|
|
548
|
+
method: 'GET',
|
|
549
|
+
headers: { 'Content-Type': 'application/json' }
|
|
550
|
+
});
|
|
551
|
+
const data = await response.json();
|
|
552
|
+
return { available: data.available || false, suggestion: data.suggestion };
|
|
715
553
|
}
|
|
716
|
-
catch
|
|
717
|
-
|
|
718
|
-
this.activeAuthServer = this.getDefaultAuthServer();
|
|
554
|
+
catch {
|
|
555
|
+
return { available: true };
|
|
719
556
|
}
|
|
720
557
|
}
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
558
|
+
async checkOnboardingStatus(identityId, platformCode) {
|
|
559
|
+
const accountsUrl = this.getAccountsUrl();
|
|
560
|
+
const platform = platformCode ?? this.config.platformCode ?? '';
|
|
561
|
+
const response = await fetch(`${accountsUrl}/api/auth/onboarding/status?platform_code=${platform}&identity_id=${identityId}`, { method: 'GET', headers: { 'Content-Type': 'application/json' }, credentials: 'include' });
|
|
562
|
+
if (!response.ok)
|
|
563
|
+
throw new Error('Failed to check onboarding status');
|
|
564
|
+
return response.json();
|
|
565
|
+
}
|
|
566
|
+
async completeTenantOnboarding(countryCode, tenantName, accessToken) {
|
|
567
|
+
const apiUrl = this.getPlatformApiUrl();
|
|
568
|
+
const response = await fetch(`${apiUrl}/auth/register-tenant`, {
|
|
569
|
+
method: 'POST',
|
|
570
|
+
headers: {
|
|
571
|
+
'Content-Type': 'application/json',
|
|
572
|
+
'Authorization': `Bearer ${accessToken}`
|
|
573
|
+
},
|
|
574
|
+
credentials: 'include',
|
|
575
|
+
body: JSON.stringify({
|
|
576
|
+
platform: this.config.platformCode,
|
|
577
|
+
tenant_name: tenantName,
|
|
578
|
+
country_code: countryCode,
|
|
579
|
+
provider: 'google',
|
|
580
|
+
oauth_token: accessToken
|
|
581
|
+
})
|
|
582
|
+
});
|
|
583
|
+
if (!response.ok) {
|
|
584
|
+
const errorData = await response.json();
|
|
585
|
+
throw new Error(errorData.message || 'Failed to create tenant');
|
|
586
|
+
}
|
|
587
|
+
return response.json();
|
|
588
|
+
}
|
|
589
|
+
async checkEmail(email) {
|
|
725
590
|
try {
|
|
726
|
-
|
|
727
|
-
|
|
591
|
+
const accountsUrl = this.getAccountsUrl();
|
|
592
|
+
const response = await fetch(`${accountsUrl}/api/auth/check-email`, {
|
|
593
|
+
method: 'POST',
|
|
594
|
+
headers: { 'Content-Type': 'application/json' },
|
|
595
|
+
body: JSON.stringify({ email })
|
|
596
|
+
});
|
|
597
|
+
const data = await response.json();
|
|
598
|
+
return { exists: data.exists, user: data.user };
|
|
728
599
|
}
|
|
729
|
-
catch
|
|
730
|
-
|
|
600
|
+
catch {
|
|
601
|
+
return { exists: false };
|
|
731
602
|
}
|
|
732
603
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Configure the ngx-stonescriptphp-client library.
|
|
608
|
+
*
|
|
609
|
+
* @param environment - Library configuration (API server, auth settings, etc.)
|
|
610
|
+
* @param plugin - Optional auth plugin override. Defaults to StoneScriptPHPAuth.
|
|
611
|
+
* Provide your own plugin to use Firebase, progalaxyelabs-auth, Okta, or any other auth backend.
|
|
612
|
+
*
|
|
613
|
+
* @example Default (StoneScriptPHP backend)
|
|
614
|
+
* ```typescript
|
|
615
|
+
* // app.config.ts
|
|
616
|
+
* export const appConfig: ApplicationConfig = {
|
|
617
|
+
* providers: [
|
|
618
|
+
* provideNgxStoneScriptPhpClient(environment)
|
|
619
|
+
* ]
|
|
620
|
+
* };
|
|
621
|
+
* ```
|
|
622
|
+
*
|
|
623
|
+
* @example External auth plugin
|
|
624
|
+
* ```typescript
|
|
625
|
+
* import { ProgalaxyElabsAuth } from './progalaxyelabs-auth.auth-plugin';
|
|
626
|
+
*
|
|
627
|
+
* providers: [
|
|
628
|
+
* provideNgxStoneScriptPhpClient(environment, new ProgalaxyElabsAuth({ host: '...' }))
|
|
629
|
+
* ]
|
|
630
|
+
* ```
|
|
631
|
+
*
|
|
632
|
+
* @example Firebase
|
|
633
|
+
* ```typescript
|
|
634
|
+
* import { FirebaseAuthPlugin } from './firebase-auth.auth-plugin';
|
|
635
|
+
*
|
|
636
|
+
* providers: [
|
|
637
|
+
* provideNgxStoneScriptPhpClient(environment, new FirebaseAuthPlugin(firebaseConfig))
|
|
638
|
+
* ]
|
|
639
|
+
* ```
|
|
640
|
+
*/
|
|
641
|
+
function provideNgxStoneScriptPhpClient(environment, plugin) {
|
|
642
|
+
const resolvedPlugin = plugin ?? new StoneScriptPHPAuth({
|
|
643
|
+
// Resolve auth host: auth.host → accountsServer.host (compat) → accountsUrl (compat) → apiServer.host
|
|
644
|
+
host: environment.auth?.host
|
|
645
|
+
|| environment.accountsServer?.host
|
|
646
|
+
|| environment.accountsUrl
|
|
647
|
+
|| environment.apiServer.host,
|
|
648
|
+
platformCode: environment.platformCode,
|
|
649
|
+
authServers: environment.authServers,
|
|
650
|
+
responseMap: environment.auth?.responseMap ?? environment.authResponseMap,
|
|
651
|
+
auth: environment.auth,
|
|
652
|
+
apiUrl: environment.apiUrl
|
|
653
|
+
});
|
|
654
|
+
return makeEnvironmentProviders([
|
|
655
|
+
{ provide: MyEnvironmentModel, useValue: environment },
|
|
656
|
+
{ provide: AUTH_PLUGIN, useValue: resolvedPlugin }
|
|
657
|
+
]);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
class ApiResponse {
|
|
661
|
+
status;
|
|
662
|
+
data;
|
|
663
|
+
message;
|
|
664
|
+
get success() {
|
|
665
|
+
return this.status === 'ok';
|
|
666
|
+
}
|
|
667
|
+
get errors() {
|
|
668
|
+
return this.message ? [this.message] : [];
|
|
669
|
+
}
|
|
670
|
+
constructor(status, data = null, message = '') {
|
|
671
|
+
this.status = status;
|
|
672
|
+
this.data = data || null;
|
|
673
|
+
this.message = message;
|
|
674
|
+
}
|
|
675
|
+
onOk(callback) {
|
|
676
|
+
if (this.status === 'ok') {
|
|
677
|
+
callback(this.data);
|
|
740
678
|
}
|
|
741
|
-
return
|
|
679
|
+
return this;
|
|
742
680
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
return this.activeAuthServer;
|
|
681
|
+
onNotOk(callback) {
|
|
682
|
+
if (this.status === 'not ok') {
|
|
683
|
+
callback(this.message, this.data);
|
|
684
|
+
}
|
|
685
|
+
return this;
|
|
749
686
|
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
* @throws Error if server not found in configuration
|
|
754
|
-
*/
|
|
755
|
-
switchAuthServer(serverName) {
|
|
756
|
-
if (!this.environment.authServers) {
|
|
757
|
-
throw new Error('Multi-server mode not configured. Use authServers in environment config.');
|
|
687
|
+
onError(callback) {
|
|
688
|
+
if (this.status === 'error') {
|
|
689
|
+
callback();
|
|
758
690
|
}
|
|
759
|
-
|
|
760
|
-
|
|
691
|
+
return this;
|
|
692
|
+
}
|
|
693
|
+
isSuccess() {
|
|
694
|
+
return this.status === 'ok';
|
|
695
|
+
}
|
|
696
|
+
isError() {
|
|
697
|
+
return this.status === 'error' || this.status === 'not ok';
|
|
698
|
+
}
|
|
699
|
+
getData() {
|
|
700
|
+
return this.data || null;
|
|
701
|
+
}
|
|
702
|
+
getError() {
|
|
703
|
+
return this.message || 'Unknown error';
|
|
704
|
+
}
|
|
705
|
+
getStatus() {
|
|
706
|
+
return this.status;
|
|
707
|
+
}
|
|
708
|
+
getMessage() {
|
|
709
|
+
return this.message;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
class TokenService {
|
|
714
|
+
accessToken = '';
|
|
715
|
+
refreshToken = '';
|
|
716
|
+
lsAccessTokenKey = 'progalaxyapi_access_token';
|
|
717
|
+
lsRefreshTokenKey = 'progalaxyapi_refresh_token';
|
|
718
|
+
constructor() { }
|
|
719
|
+
setTokens(accessToken, refreshToken) {
|
|
720
|
+
this.accessToken = accessToken;
|
|
721
|
+
this.refreshToken = refreshToken;
|
|
722
|
+
localStorage.setItem(this.lsAccessTokenKey, accessToken);
|
|
723
|
+
localStorage.setItem(this.lsRefreshTokenKey, refreshToken);
|
|
724
|
+
}
|
|
725
|
+
setAccessToken(accessToken) {
|
|
726
|
+
this.accessToken = accessToken;
|
|
727
|
+
localStorage.setItem(this.lsAccessTokenKey, accessToken);
|
|
728
|
+
}
|
|
729
|
+
setRefreshToken(refreshToken) {
|
|
730
|
+
this.refreshToken = refreshToken;
|
|
731
|
+
localStorage.setItem(this.lsRefreshTokenKey, refreshToken);
|
|
732
|
+
}
|
|
733
|
+
getAccessToken() {
|
|
734
|
+
if (this.accessToken) {
|
|
735
|
+
return this.accessToken;
|
|
736
|
+
}
|
|
737
|
+
const storedAccessToken = localStorage.getItem(this.lsAccessTokenKey);
|
|
738
|
+
if (storedAccessToken) {
|
|
739
|
+
return storedAccessToken;
|
|
740
|
+
}
|
|
741
|
+
else {
|
|
742
|
+
return '';
|
|
761
743
|
}
|
|
762
|
-
this.saveActiveAuthServer(serverName);
|
|
763
744
|
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
*/
|
|
768
|
-
getAuthServerConfig(serverName) {
|
|
769
|
-
if (!this.environment.authServers) {
|
|
770
|
-
return null;
|
|
745
|
+
getRefreshToken() {
|
|
746
|
+
if (this.refreshToken) {
|
|
747
|
+
return this.refreshToken;
|
|
771
748
|
}
|
|
772
|
-
const
|
|
773
|
-
if (
|
|
774
|
-
return
|
|
749
|
+
const storedRefreshToken = localStorage.getItem(this.lsRefreshTokenKey);
|
|
750
|
+
if (storedRefreshToken) {
|
|
751
|
+
return storedRefreshToken;
|
|
752
|
+
}
|
|
753
|
+
else {
|
|
754
|
+
return '';
|
|
775
755
|
}
|
|
776
|
-
return this.environment.authServers[targetServer] || null;
|
|
777
756
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
757
|
+
clear() {
|
|
758
|
+
this.accessToken = '';
|
|
759
|
+
this.refreshToken = '';
|
|
760
|
+
localStorage.removeItem(this.lsAccessTokenKey);
|
|
761
|
+
localStorage.removeItem(this.lsRefreshTokenKey);
|
|
783
762
|
}
|
|
784
763
|
/**
|
|
785
|
-
*
|
|
786
|
-
*
|
|
787
|
-
* @throws Error if no API URL is configured
|
|
764
|
+
* Check if there is a non-empty access token.
|
|
765
|
+
* Token is treated as opaque — validity is determined by the auth server.
|
|
788
766
|
*/
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
767
|
+
hasValidAccessToken() {
|
|
768
|
+
const token = this.getAccessToken();
|
|
769
|
+
return token !== null && token !== '';
|
|
770
|
+
}
|
|
771
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TokenService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
772
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TokenService, providedIn: 'root' });
|
|
773
|
+
}
|
|
774
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TokenService, decorators: [{
|
|
775
|
+
type: Injectable,
|
|
776
|
+
args: [{
|
|
777
|
+
providedIn: 'root'
|
|
778
|
+
}]
|
|
779
|
+
}], ctorParameters: () => [] });
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* @deprecated Use boolean directly. Kept for backward compatibility.
|
|
783
|
+
*/
|
|
784
|
+
var VerifyStatus;
|
|
785
|
+
(function (VerifyStatus) {
|
|
786
|
+
VerifyStatus["initialized"] = "initialized";
|
|
787
|
+
VerifyStatus["yes"] = "yes";
|
|
788
|
+
VerifyStatus["no"] = "no";
|
|
789
|
+
})(VerifyStatus || (VerifyStatus = {}));
|
|
790
|
+
class SigninStatusService {
|
|
791
|
+
status;
|
|
792
|
+
constructor() {
|
|
793
|
+
this.status = new BehaviorSubject(false);
|
|
794
|
+
}
|
|
795
|
+
signedOut() {
|
|
796
|
+
this.status.next(false);
|
|
797
|
+
}
|
|
798
|
+
signedIn() {
|
|
799
|
+
this.status.next(true);
|
|
797
800
|
}
|
|
798
801
|
/**
|
|
799
|
-
*
|
|
800
|
-
*
|
|
802
|
+
* Set signin status
|
|
803
|
+
* @param isSignedIn - True if user is signed in, false otherwise
|
|
801
804
|
*/
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
805
|
+
setSigninStatus(isSignedIn) {
|
|
806
|
+
this.status.next(isSignedIn);
|
|
807
|
+
}
|
|
808
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SigninStatusService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
809
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SigninStatusService, providedIn: 'root' });
|
|
810
|
+
}
|
|
811
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SigninStatusService, decorators: [{
|
|
812
|
+
type: Injectable,
|
|
813
|
+
args: [{
|
|
814
|
+
providedIn: 'root'
|
|
815
|
+
}]
|
|
816
|
+
}], ctorParameters: () => [] });
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* AuthService — manages auth state and delegates all auth operations to the AuthPlugin.
|
|
820
|
+
*
|
|
821
|
+
* This service holds user state (via BehaviorSubject) and tokens (via TokenService).
|
|
822
|
+
* It does not make any HTTP calls directly — all auth logic lives in the plugin.
|
|
823
|
+
*
|
|
824
|
+
* Provide a plugin via provideNgxStoneScriptPhpClient():
|
|
825
|
+
* - Default: StoneScriptPHPAuth (built-in, matches StoneScriptPHP backend)
|
|
826
|
+
* - External: any class implementing AuthPlugin (Firebase, progalaxyelabs-auth, Okta, etc.)
|
|
827
|
+
*/
|
|
828
|
+
class AuthService {
|
|
829
|
+
plugin;
|
|
830
|
+
tokens;
|
|
831
|
+
signinStatus;
|
|
832
|
+
USER_STORAGE_KEY = 'progalaxyapi_user';
|
|
833
|
+
userSubject = new BehaviorSubject(null);
|
|
834
|
+
user$ = this.userSubject.asObservable();
|
|
835
|
+
constructor(plugin, tokens, signinStatus) {
|
|
836
|
+
this.plugin = plugin;
|
|
837
|
+
this.tokens = tokens;
|
|
838
|
+
this.signinStatus = signinStatus;
|
|
839
|
+
this.restoreUser();
|
|
810
840
|
}
|
|
811
|
-
|
|
812
|
-
* Restore user from localStorage
|
|
813
|
-
*/
|
|
841
|
+
// ── State management ──────────────────────────────────────────────────────
|
|
814
842
|
restoreUser() {
|
|
815
843
|
try {
|
|
816
844
|
const userJson = localStorage.getItem(this.USER_STORAGE_KEY);
|
|
817
|
-
if (userJson)
|
|
818
|
-
|
|
819
|
-
this.updateUser(user);
|
|
820
|
-
}
|
|
845
|
+
if (userJson)
|
|
846
|
+
this.updateUser(JSON.parse(userJson));
|
|
821
847
|
}
|
|
822
848
|
catch (error) {
|
|
823
849
|
console.error('Failed to restore user from localStorage:', error);
|
|
824
850
|
}
|
|
825
851
|
}
|
|
826
|
-
/**
|
|
827
|
-
* Save user to localStorage
|
|
828
|
-
*/
|
|
829
852
|
saveUser(user) {
|
|
830
853
|
try {
|
|
831
854
|
if (user) {
|
|
@@ -839,720 +862,431 @@ class AuthService {
|
|
|
839
862
|
console.error('Failed to save user to localStorage:', error);
|
|
840
863
|
}
|
|
841
864
|
}
|
|
842
|
-
/**
|
|
843
|
-
* Update user subject and persist to localStorage
|
|
844
|
-
*/
|
|
845
865
|
updateUser(user) {
|
|
846
866
|
this.userSubject.next(user);
|
|
847
867
|
this.saveUser(user);
|
|
848
868
|
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
password,
|
|
865
|
-
platform: this.environment.platformCode
|
|
866
|
-
})
|
|
867
|
-
});
|
|
868
|
-
const data = await response.json();
|
|
869
|
-
if (data.success && data.access_token) {
|
|
870
|
-
this.tokens.setAccessToken(data.access_token);
|
|
871
|
-
this.signinStatus.setSigninStatus(true);
|
|
872
|
-
// Normalize user object to handle both response formats
|
|
873
|
-
const normalizedUser = {
|
|
874
|
-
user_id: data.user.user_id ?? (data.user.id ? this.hashUUID(data.user.id) : 0),
|
|
875
|
-
id: data.user.id ?? String(data.user.user_id),
|
|
876
|
-
email: data.user.email,
|
|
877
|
-
display_name: data.user.display_name ?? data.user.email.split('@')[0],
|
|
878
|
-
photo_url: data.user.photo_url,
|
|
879
|
-
is_email_verified: data.user.is_email_verified ?? false
|
|
880
|
-
};
|
|
881
|
-
this.updateUser(normalizedUser);
|
|
882
|
-
return { success: true, user: normalizedUser };
|
|
883
|
-
}
|
|
884
|
-
return {
|
|
885
|
-
success: false,
|
|
886
|
-
message: data.message || 'Invalid credentials'
|
|
887
|
-
};
|
|
888
|
-
}
|
|
889
|
-
catch (error) {
|
|
890
|
-
return {
|
|
891
|
-
success: false,
|
|
892
|
-
message: 'Network error. Please try again.'
|
|
893
|
-
};
|
|
894
|
-
}
|
|
869
|
+
storeAuthResult(result) {
|
|
870
|
+
if (result.accessToken)
|
|
871
|
+
this.tokens.setAccessToken(result.accessToken);
|
|
872
|
+
if (result.refreshToken)
|
|
873
|
+
this.tokens.setRefreshToken(result.refreshToken);
|
|
874
|
+
if (result.user)
|
|
875
|
+
this.updateUser(result.user);
|
|
876
|
+
this.signinStatus.setSigninStatus(true);
|
|
877
|
+
}
|
|
878
|
+
// ── Core auth operations ──────────────────────────────────────────────────
|
|
879
|
+
async loginWithEmail(email, password) {
|
|
880
|
+
const result = await this.plugin.login(email, password);
|
|
881
|
+
if (result.success)
|
|
882
|
+
this.storeAuthResult(result);
|
|
883
|
+
return result;
|
|
895
884
|
}
|
|
896
|
-
/**
|
|
897
|
-
* Login with Google OAuth (popup window)
|
|
898
|
-
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
899
|
-
*/
|
|
900
885
|
async loginWithGoogle(serverName) {
|
|
901
|
-
return this.
|
|
886
|
+
return this.loginWithProvider('google');
|
|
902
887
|
}
|
|
903
|
-
/**
|
|
904
|
-
* Login with GitHub OAuth (popup window)
|
|
905
|
-
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
906
|
-
*/
|
|
907
888
|
async loginWithGitHub(serverName) {
|
|
908
|
-
return this.
|
|
889
|
+
return this.loginWithProvider('github');
|
|
909
890
|
}
|
|
910
|
-
/**
|
|
911
|
-
* Login with LinkedIn OAuth (popup window)
|
|
912
|
-
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
913
|
-
*/
|
|
914
891
|
async loginWithLinkedIn(serverName) {
|
|
915
|
-
return this.
|
|
892
|
+
return this.loginWithProvider('linkedin');
|
|
916
893
|
}
|
|
917
|
-
/**
|
|
918
|
-
* Login with Apple OAuth (popup window)
|
|
919
|
-
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
920
|
-
*/
|
|
921
894
|
async loginWithApple(serverName) {
|
|
922
|
-
return this.
|
|
895
|
+
return this.loginWithProvider('apple');
|
|
923
896
|
}
|
|
924
|
-
/**
|
|
925
|
-
* Login with Microsoft OAuth (popup window)
|
|
926
|
-
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
927
|
-
*/
|
|
928
897
|
async loginWithMicrosoft(serverName) {
|
|
929
|
-
return this.
|
|
898
|
+
return this.loginWithProvider('microsoft');
|
|
930
899
|
}
|
|
931
|
-
/**
|
|
932
|
-
* Login with Zoho OAuth (popup window)
|
|
933
|
-
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
934
|
-
*/
|
|
935
900
|
async loginWithZoho(serverName) {
|
|
936
|
-
return this.
|
|
901
|
+
return this.loginWithProvider('zoho');
|
|
937
902
|
}
|
|
938
|
-
/**
|
|
939
|
-
* Generic provider-based login (supports all OAuth providers)
|
|
940
|
-
* @param provider - The provider identifier
|
|
941
|
-
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
942
|
-
*/
|
|
943
903
|
async loginWithProvider(provider, serverName) {
|
|
944
904
|
if (provider === 'emailPassword') {
|
|
945
905
|
throw new Error('Use loginWithEmail() for email/password authentication');
|
|
946
906
|
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
*/
|
|
955
|
-
async loginWithOAuth(provider, serverName) {
|
|
956
|
-
return new Promise((resolve) => {
|
|
957
|
-
const width = 500;
|
|
958
|
-
const height = 600;
|
|
959
|
-
const left = (window.screen.width - width) / 2;
|
|
960
|
-
const top = (window.screen.height - height) / 2;
|
|
961
|
-
const accountsUrl = this.getAccountsUrl(serverName);
|
|
962
|
-
const oauthUrl = `${accountsUrl}/oauth/${provider}?` +
|
|
963
|
-
`platform=${this.environment.platformCode}&` +
|
|
964
|
-
`mode=popup`;
|
|
965
|
-
const popup = window.open(oauthUrl, `${provider}_login`, `width=${width},height=${height},left=${left},top=${top}`);
|
|
966
|
-
if (!popup) {
|
|
967
|
-
resolve({
|
|
968
|
-
success: false,
|
|
969
|
-
message: 'Popup blocked. Please allow popups for this site.'
|
|
970
|
-
});
|
|
971
|
-
return;
|
|
972
|
-
}
|
|
973
|
-
// Listen for message from popup
|
|
974
|
-
const messageHandler = (event) => {
|
|
975
|
-
// Verify origin
|
|
976
|
-
if (event.origin !== new URL(accountsUrl).origin) {
|
|
977
|
-
return;
|
|
978
|
-
}
|
|
979
|
-
if (event.data.type === 'oauth_success') {
|
|
980
|
-
this.tokens.setAccessToken(event.data.access_token);
|
|
981
|
-
this.signinStatus.setSigninStatus(true);
|
|
982
|
-
// Normalize user object to handle both response formats
|
|
983
|
-
const normalizedUser = {
|
|
984
|
-
user_id: event.data.user.user_id ?? (event.data.user.id ? this.hashUUID(event.data.user.id) : 0),
|
|
985
|
-
id: event.data.user.id ?? String(event.data.user.user_id),
|
|
986
|
-
email: event.data.user.email,
|
|
987
|
-
display_name: event.data.user.display_name ?? event.data.user.email.split('@')[0],
|
|
988
|
-
photo_url: event.data.user.photo_url,
|
|
989
|
-
is_email_verified: event.data.user.is_email_verified ?? false
|
|
990
|
-
};
|
|
991
|
-
this.updateUser(normalizedUser);
|
|
992
|
-
window.removeEventListener('message', messageHandler);
|
|
993
|
-
popup.close();
|
|
994
|
-
resolve({
|
|
995
|
-
success: true,
|
|
996
|
-
user: normalizedUser
|
|
997
|
-
});
|
|
998
|
-
}
|
|
999
|
-
else if (event.data.type === 'oauth_error') {
|
|
1000
|
-
window.removeEventListener('message', messageHandler);
|
|
1001
|
-
popup.close();
|
|
1002
|
-
resolve({
|
|
1003
|
-
success: false,
|
|
1004
|
-
message: event.data.message || 'OAuth login failed'
|
|
1005
|
-
});
|
|
1006
|
-
}
|
|
1007
|
-
};
|
|
1008
|
-
window.addEventListener('message', messageHandler);
|
|
1009
|
-
// Check if popup was closed manually
|
|
1010
|
-
const checkClosed = setInterval(() => {
|
|
1011
|
-
if (popup.closed) {
|
|
1012
|
-
clearInterval(checkClosed);
|
|
1013
|
-
window.removeEventListener('message', messageHandler);
|
|
1014
|
-
resolve({
|
|
1015
|
-
success: false,
|
|
1016
|
-
message: 'Login cancelled'
|
|
1017
|
-
});
|
|
1018
|
-
}
|
|
1019
|
-
}, 500);
|
|
1020
|
-
});
|
|
907
|
+
if (!this.plugin.loginWithProvider) {
|
|
908
|
+
return { success: false, message: 'OAuth not supported by the configured auth plugin' };
|
|
909
|
+
}
|
|
910
|
+
const result = await this.plugin.loginWithProvider(provider);
|
|
911
|
+
if (result.success)
|
|
912
|
+
this.storeAuthResult(result);
|
|
913
|
+
return result;
|
|
1021
914
|
}
|
|
1022
|
-
/**
|
|
1023
|
-
* Register new user
|
|
1024
|
-
* @param email - User email
|
|
1025
|
-
* @param password - User password
|
|
1026
|
-
* @param displayName - Display name
|
|
1027
|
-
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
1028
|
-
*/
|
|
1029
915
|
async register(email, password, displayName, serverName) {
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1035
|
-
credentials: 'include',
|
|
1036
|
-
body: JSON.stringify({
|
|
1037
|
-
email,
|
|
1038
|
-
password,
|
|
1039
|
-
display_name: displayName,
|
|
1040
|
-
platform: this.environment.platformCode
|
|
1041
|
-
})
|
|
1042
|
-
});
|
|
1043
|
-
const data = await response.json();
|
|
1044
|
-
if (data.success && data.access_token) {
|
|
1045
|
-
this.tokens.setAccessToken(data.access_token);
|
|
1046
|
-
this.signinStatus.setSigninStatus(true);
|
|
1047
|
-
// Normalize user object to handle both response formats
|
|
1048
|
-
const normalizedUser = {
|
|
1049
|
-
user_id: data.user.user_id ?? (data.user.id ? this.hashUUID(data.user.id) : 0),
|
|
1050
|
-
id: data.user.id ?? String(data.user.user_id),
|
|
1051
|
-
email: data.user.email,
|
|
1052
|
-
display_name: data.user.display_name ?? data.user.email.split('@')[0],
|
|
1053
|
-
photo_url: data.user.photo_url,
|
|
1054
|
-
is_email_verified: data.user.is_email_verified ?? false
|
|
1055
|
-
};
|
|
1056
|
-
this.updateUser(normalizedUser);
|
|
1057
|
-
return {
|
|
1058
|
-
success: true,
|
|
1059
|
-
user: normalizedUser,
|
|
1060
|
-
message: data.needs_verification ? 'Please verify your email' : undefined
|
|
1061
|
-
};
|
|
1062
|
-
}
|
|
1063
|
-
return {
|
|
1064
|
-
success: false,
|
|
1065
|
-
message: data.message || 'Registration failed'
|
|
1066
|
-
};
|
|
1067
|
-
}
|
|
1068
|
-
catch (error) {
|
|
1069
|
-
return {
|
|
1070
|
-
success: false,
|
|
1071
|
-
message: 'Network error. Please try again.'
|
|
1072
|
-
};
|
|
1073
|
-
}
|
|
916
|
+
const result = await this.plugin.register(email, password, displayName);
|
|
917
|
+
if (result.success)
|
|
918
|
+
this.storeAuthResult(result);
|
|
919
|
+
return result;
|
|
1074
920
|
}
|
|
1075
|
-
/**
|
|
1076
|
-
* Sign out user
|
|
1077
|
-
* @param serverName - Optional: Specify which auth server to logout from (for multi-server mode)
|
|
1078
|
-
*/
|
|
1079
921
|
async signout(serverName) {
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
method: 'POST',
|
|
1086
|
-
headers: {
|
|
1087
|
-
'Content-Type': 'application/json'
|
|
1088
|
-
},
|
|
1089
|
-
credentials: 'include',
|
|
1090
|
-
body: JSON.stringify({
|
|
1091
|
-
refresh_token: refreshToken
|
|
1092
|
-
})
|
|
1093
|
-
});
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
catch (error) {
|
|
1097
|
-
console.error('Logout API call failed:', error);
|
|
1098
|
-
}
|
|
1099
|
-
finally {
|
|
1100
|
-
this.tokens.clear();
|
|
1101
|
-
this.signinStatus.setSigninStatus(false);
|
|
1102
|
-
this.updateUser(null);
|
|
1103
|
-
}
|
|
922
|
+
const refreshToken = this.tokens.getRefreshToken() || undefined;
|
|
923
|
+
await this.plugin.logout(refreshToken);
|
|
924
|
+
this.tokens.clear();
|
|
925
|
+
this.signinStatus.setSigninStatus(false);
|
|
926
|
+
this.updateUser(null);
|
|
1104
927
|
}
|
|
1105
|
-
/**
|
|
1106
|
-
* Check for active session (call on app init)
|
|
1107
|
-
* @param serverName - Optional: Specify which auth server to check (for multi-server mode)
|
|
1108
|
-
*/
|
|
1109
928
|
async checkSession(serverName) {
|
|
1110
929
|
if (this.tokens.hasValidAccessToken()) {
|
|
1111
930
|
this.signinStatus.setSigninStatus(true);
|
|
1112
931
|
return true;
|
|
1113
932
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
if (!response.ok) {
|
|
1122
|
-
this.signinStatus.setSigninStatus(false);
|
|
1123
|
-
return false;
|
|
1124
|
-
}
|
|
1125
|
-
const data = await response.json();
|
|
1126
|
-
if (data.access_token) {
|
|
1127
|
-
this.tokens.setAccessToken(data.access_token);
|
|
1128
|
-
// Normalize user object to handle both response formats
|
|
1129
|
-
if (data.user) {
|
|
1130
|
-
const normalizedUser = {
|
|
1131
|
-
user_id: data.user.user_id ?? (data.user.id ? this.hashUUID(data.user.id) : 0),
|
|
1132
|
-
id: data.user.id ?? String(data.user.user_id),
|
|
1133
|
-
email: data.user.email,
|
|
1134
|
-
display_name: data.user.display_name ?? data.user.email.split('@')[0],
|
|
1135
|
-
photo_url: data.user.photo_url,
|
|
1136
|
-
is_email_verified: data.user.is_email_verified ?? false
|
|
1137
|
-
};
|
|
1138
|
-
this.updateUser(normalizedUser);
|
|
1139
|
-
}
|
|
1140
|
-
this.signinStatus.setSigninStatus(true);
|
|
1141
|
-
return true;
|
|
1142
|
-
}
|
|
1143
|
-
return false;
|
|
1144
|
-
}
|
|
1145
|
-
catch (error) {
|
|
1146
|
-
this.signinStatus.setSigninStatus(false);
|
|
1147
|
-
return false;
|
|
933
|
+
const result = await this.plugin.checkSession();
|
|
934
|
+
if (result.success && result.accessToken) {
|
|
935
|
+
this.tokens.setAccessToken(result.accessToken);
|
|
936
|
+
if (result.user)
|
|
937
|
+
this.updateUser(result.user);
|
|
938
|
+
this.signinStatus.setSigninStatus(true);
|
|
939
|
+
return true;
|
|
1148
940
|
}
|
|
941
|
+
this.tokens.clear();
|
|
942
|
+
this.updateUser(null);
|
|
943
|
+
this.signinStatus.setSigninStatus(false);
|
|
944
|
+
return false;
|
|
1149
945
|
}
|
|
1150
946
|
/**
|
|
1151
|
-
*
|
|
1152
|
-
|
|
1153
|
-
isAuthenticated() {
|
|
1154
|
-
return this.tokens.hasValidAccessToken();
|
|
1155
|
-
}
|
|
1156
|
-
/**
|
|
1157
|
-
* Get current user (synchronous)
|
|
1158
|
-
*/
|
|
1159
|
-
getCurrentUser() {
|
|
1160
|
-
return this.userSubject.value;
|
|
1161
|
-
}
|
|
1162
|
-
// ===== Multi-Tenant Methods =====
|
|
1163
|
-
/**
|
|
1164
|
-
* Register a new user AND create a new tenant (organization)
|
|
1165
|
-
* This is used when a user wants to create their own organization
|
|
947
|
+
* Refresh the access token. Called by ApiConnectionService on 401.
|
|
948
|
+
* @returns true if token was refreshed, false if refresh failed (user is signed out)
|
|
1166
949
|
*/
|
|
1167
|
-
async
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
}
|
|
1173
|
-
// Email/password registration — route through platform API proxy
|
|
1174
|
-
const apiUrl = this.getPlatformApiUrl();
|
|
1175
|
-
const response = await fetch(`${apiUrl}/auth/register-tenant`, {
|
|
1176
|
-
method: 'POST',
|
|
1177
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1178
|
-
credentials: 'include',
|
|
1179
|
-
body: JSON.stringify({
|
|
1180
|
-
platform: this.environment.platformCode,
|
|
1181
|
-
tenant_name: data.tenantName,
|
|
1182
|
-
tenant_slug: data.tenantSlug,
|
|
1183
|
-
display_name: data.displayName,
|
|
1184
|
-
email: data.email,
|
|
1185
|
-
password: data.password,
|
|
1186
|
-
provider: 'emailPassword'
|
|
1187
|
-
})
|
|
1188
|
-
});
|
|
1189
|
-
const result = await response.json();
|
|
1190
|
-
if (result.success && result.access_token) {
|
|
1191
|
-
this.tokens.setAccessToken(result.access_token);
|
|
1192
|
-
this.signinStatus.setSigninStatus(true);
|
|
1193
|
-
if (result.user) {
|
|
1194
|
-
// Normalize user object to handle both response formats
|
|
1195
|
-
const normalizedUser = {
|
|
1196
|
-
user_id: result.user.user_id ?? (result.user.id ? this.hashUUID(result.user.id) : 0),
|
|
1197
|
-
id: result.user.id ?? String(result.user.user_id),
|
|
1198
|
-
email: result.user.email,
|
|
1199
|
-
display_name: result.user.display_name ?? result.user.email.split('@')[0],
|
|
1200
|
-
photo_url: result.user.photo_url,
|
|
1201
|
-
is_email_verified: result.user.is_email_verified ?? false
|
|
1202
|
-
};
|
|
1203
|
-
this.updateUser(normalizedUser);
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
return result;
|
|
1207
|
-
}
|
|
1208
|
-
catch (error) {
|
|
1209
|
-
return {
|
|
1210
|
-
success: false,
|
|
1211
|
-
message: 'Network error. Please try again.'
|
|
1212
|
-
};
|
|
950
|
+
async refresh() {
|
|
951
|
+
const newToken = await this.plugin.refresh(this.tokens.getAccessToken(), this.tokens.getRefreshToken() || undefined);
|
|
952
|
+
if (newToken) {
|
|
953
|
+
this.tokens.setAccessToken(newToken);
|
|
954
|
+
return true;
|
|
1213
955
|
}
|
|
956
|
+
this.tokens.clear();
|
|
957
|
+
this.updateUser(null);
|
|
958
|
+
this.signinStatus.signedOut();
|
|
959
|
+
return false;
|
|
1214
960
|
}
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
* Opens popup window for OAuth flow
|
|
1218
|
-
*/
|
|
1219
|
-
async registerTenantWithOAuth(tenantName, tenantSlug, provider) {
|
|
1220
|
-
return new Promise((resolve) => {
|
|
1221
|
-
const width = 500;
|
|
1222
|
-
const height = 600;
|
|
1223
|
-
const left = (window.screen.width - width) / 2;
|
|
1224
|
-
const top = (window.screen.height - height) / 2;
|
|
1225
|
-
// Build OAuth URL with tenant registration params
|
|
1226
|
-
const accountsUrl = this.getAccountsUrl();
|
|
1227
|
-
const oauthUrl = `${accountsUrl}/oauth/${provider}?` +
|
|
1228
|
-
`platform=${this.environment.platformCode}&` +
|
|
1229
|
-
`mode=popup&` +
|
|
1230
|
-
`action=register_tenant&` +
|
|
1231
|
-
`tenant_name=${encodeURIComponent(tenantName)}&` +
|
|
1232
|
-
`tenant_slug=${encodeURIComponent(tenantSlug)}`;
|
|
1233
|
-
const popup = window.open(oauthUrl, `${provider}_register_tenant`, `width=${width},height=${height},left=${left},top=${top}`);
|
|
1234
|
-
if (!popup) {
|
|
1235
|
-
resolve({
|
|
1236
|
-
success: false,
|
|
1237
|
-
message: 'Popup blocked. Please allow popups for this site.'
|
|
1238
|
-
});
|
|
1239
|
-
return;
|
|
1240
|
-
}
|
|
1241
|
-
// Listen for message from popup
|
|
1242
|
-
const messageHandler = (event) => {
|
|
1243
|
-
// Verify origin
|
|
1244
|
-
if (event.origin !== new URL(accountsUrl).origin) {
|
|
1245
|
-
return;
|
|
1246
|
-
}
|
|
1247
|
-
if (event.data.type === 'tenant_register_success') {
|
|
1248
|
-
// Set tokens and user
|
|
1249
|
-
if (event.data.access_token) {
|
|
1250
|
-
this.tokens.setAccessToken(event.data.access_token);
|
|
1251
|
-
this.signinStatus.setSigninStatus(true);
|
|
1252
|
-
}
|
|
1253
|
-
if (event.data.user) {
|
|
1254
|
-
// Normalize user object to handle both response formats
|
|
1255
|
-
const normalizedUser = {
|
|
1256
|
-
user_id: event.data.user.user_id ?? (event.data.user.id ? this.hashUUID(event.data.user.id) : 0),
|
|
1257
|
-
id: event.data.user.id ?? String(event.data.user.user_id),
|
|
1258
|
-
email: event.data.user.email,
|
|
1259
|
-
display_name: event.data.user.display_name ?? event.data.user.email.split('@')[0],
|
|
1260
|
-
photo_url: event.data.user.photo_url,
|
|
1261
|
-
is_email_verified: event.data.user.is_email_verified ?? false
|
|
1262
|
-
};
|
|
1263
|
-
this.updateUser(normalizedUser);
|
|
1264
|
-
}
|
|
1265
|
-
window.removeEventListener('message', messageHandler);
|
|
1266
|
-
popup.close();
|
|
1267
|
-
resolve({
|
|
1268
|
-
success: true,
|
|
1269
|
-
tenant: event.data.tenant,
|
|
1270
|
-
user: event.data.user
|
|
1271
|
-
});
|
|
1272
|
-
}
|
|
1273
|
-
else if (event.data.type === 'tenant_register_error') {
|
|
1274
|
-
window.removeEventListener('message', messageHandler);
|
|
1275
|
-
popup.close();
|
|
1276
|
-
resolve({
|
|
1277
|
-
success: false,
|
|
1278
|
-
message: event.data.message || 'Tenant registration failed'
|
|
1279
|
-
});
|
|
1280
|
-
}
|
|
1281
|
-
};
|
|
1282
|
-
window.addEventListener('message', messageHandler);
|
|
1283
|
-
// Check if popup was closed manually
|
|
1284
|
-
const checkClosed = setInterval(() => {
|
|
1285
|
-
if (popup.closed) {
|
|
1286
|
-
clearInterval(checkClosed);
|
|
1287
|
-
window.removeEventListener('message', messageHandler);
|
|
1288
|
-
resolve({
|
|
1289
|
-
success: false,
|
|
1290
|
-
message: 'Registration cancelled'
|
|
1291
|
-
});
|
|
1292
|
-
}
|
|
1293
|
-
}, 500);
|
|
1294
|
-
});
|
|
1295
|
-
}
|
|
1296
|
-
/**
|
|
1297
|
-
* Get all tenant memberships for the authenticated user
|
|
1298
|
-
* @param serverName - Optional: Specify which auth server to query (for multi-server mode)
|
|
1299
|
-
*/
|
|
1300
|
-
async getTenantMemberships(serverName) {
|
|
1301
|
-
try {
|
|
1302
|
-
const accountsUrl = this.getAccountsUrl(serverName);
|
|
1303
|
-
const response = await fetch(`${accountsUrl}/api/auth/memberships`, {
|
|
1304
|
-
method: 'GET',
|
|
1305
|
-
headers: {
|
|
1306
|
-
'Authorization': `Bearer ${this.tokens.getAccessToken()}`,
|
|
1307
|
-
'Content-Type': 'application/json'
|
|
1308
|
-
},
|
|
1309
|
-
credentials: 'include'
|
|
1310
|
-
});
|
|
1311
|
-
const data = await response.json();
|
|
1312
|
-
return {
|
|
1313
|
-
memberships: data.memberships || []
|
|
1314
|
-
};
|
|
1315
|
-
}
|
|
1316
|
-
catch (error) {
|
|
1317
|
-
return { memberships: [] };
|
|
1318
|
-
}
|
|
961
|
+
isAuthenticated() {
|
|
962
|
+
return this.tokens.hasValidAccessToken();
|
|
1319
963
|
}
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
try {
|
|
1328
|
-
const accountsUrl = this.getAccountsUrl(serverName);
|
|
1329
|
-
const response = await fetch(`${accountsUrl}/api/auth/select-tenant`, {
|
|
1330
|
-
method: 'POST',
|
|
1331
|
-
headers: {
|
|
1332
|
-
'Authorization': `Bearer ${this.tokens.getAccessToken()}`,
|
|
1333
|
-
'Content-Type': 'application/json'
|
|
1334
|
-
},
|
|
1335
|
-
credentials: 'include',
|
|
1336
|
-
body: JSON.stringify({ tenant_id: tenantId })
|
|
1337
|
-
});
|
|
1338
|
-
const data = await response.json();
|
|
1339
|
-
if (data.success && data.access_token) {
|
|
1340
|
-
this.tokens.setAccessToken(data.access_token);
|
|
1341
|
-
return {
|
|
1342
|
-
success: true,
|
|
1343
|
-
access_token: data.access_token
|
|
1344
|
-
};
|
|
1345
|
-
}
|
|
1346
|
-
return {
|
|
1347
|
-
success: false,
|
|
1348
|
-
message: data.message || 'Failed to select tenant'
|
|
1349
|
-
};
|
|
964
|
+
getCurrentUser() {
|
|
965
|
+
return this.userSubject.value;
|
|
966
|
+
}
|
|
967
|
+
// ── Multi-tenant operations ───────────────────────────────────────────────
|
|
968
|
+
async registerTenant(data) {
|
|
969
|
+
if (!this.plugin.registerTenant) {
|
|
970
|
+
return { success: false, message: 'registerTenant not supported by the configured auth plugin' };
|
|
1350
971
|
}
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
972
|
+
const accessToken = this.tokens.getAccessToken() || undefined;
|
|
973
|
+
const result = await this.plugin.registerTenant(data, accessToken);
|
|
974
|
+
if (result?.access_token) {
|
|
975
|
+
this.tokens.setAccessToken(result.access_token);
|
|
976
|
+
this.signinStatus.setSigninStatus(true);
|
|
977
|
+
}
|
|
978
|
+
if (result?.refresh_token) {
|
|
979
|
+
this.tokens.setRefreshToken(result.refresh_token);
|
|
1356
980
|
}
|
|
981
|
+
return result;
|
|
1357
982
|
}
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
method: 'GET',
|
|
1368
|
-
headers: { 'Content-Type': 'application/json' }
|
|
1369
|
-
});
|
|
1370
|
-
const data = await response.json();
|
|
1371
|
-
return {
|
|
1372
|
-
available: data.available || false,
|
|
1373
|
-
suggestion: data.suggestion
|
|
1374
|
-
};
|
|
983
|
+
async getTenantMemberships(serverName) {
|
|
984
|
+
if (!this.plugin.getTenantMemberships)
|
|
985
|
+
return { memberships: [] };
|
|
986
|
+
const memberships = await this.plugin.getTenantMemberships(this.tokens.getAccessToken());
|
|
987
|
+
return { memberships };
|
|
988
|
+
}
|
|
989
|
+
async selectTenant(tenantId, serverName) {
|
|
990
|
+
if (!this.plugin.selectTenant) {
|
|
991
|
+
return { success: false, message: 'selectTenant not supported by the configured auth plugin' };
|
|
1375
992
|
}
|
|
1376
|
-
|
|
1377
|
-
|
|
993
|
+
const result = await this.plugin.selectTenant(tenantId, this.tokens.getAccessToken());
|
|
994
|
+
if (result.success && result.accessToken) {
|
|
995
|
+
this.tokens.setAccessToken(result.accessToken);
|
|
996
|
+
}
|
|
997
|
+
return { success: result.success, message: result.message, access_token: result.accessToken };
|
|
998
|
+
}
|
|
999
|
+
async checkTenantSlugAvailable(slug, serverName) {
|
|
1000
|
+
if (!this.plugin.checkTenantSlugAvailable)
|
|
1378
1001
|
return { available: true };
|
|
1002
|
+
return this.plugin.checkTenantSlugAvailable(slug);
|
|
1003
|
+
}
|
|
1004
|
+
async checkOnboardingStatus(identityId, serverName) {
|
|
1005
|
+
if (!this.plugin.checkOnboardingStatus)
|
|
1006
|
+
throw new Error('checkOnboardingStatus not supported');
|
|
1007
|
+
return this.plugin.checkOnboardingStatus(identityId);
|
|
1008
|
+
}
|
|
1009
|
+
async completeTenantOnboarding(countryCode, tenantName, serverName) {
|
|
1010
|
+
if (!this.plugin.completeTenantOnboarding)
|
|
1011
|
+
throw new Error('completeTenantOnboarding not supported');
|
|
1012
|
+
const result = await this.plugin.completeTenantOnboarding(countryCode, tenantName, this.tokens.getAccessToken());
|
|
1013
|
+
if (result?.access_token) {
|
|
1014
|
+
this.tokens.setAccessToken(result.access_token);
|
|
1015
|
+
this.signinStatus.setSigninStatus(true);
|
|
1379
1016
|
}
|
|
1017
|
+
return result;
|
|
1380
1018
|
}
|
|
1381
|
-
//
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
/**
|
|
1385
|
-
* @deprecated Use getCurrentUser()?.user_id instead
|
|
1386
|
-
*/
|
|
1387
|
-
getUserId() {
|
|
1388
|
-
return this.userSubject.value?.user_id || 0;
|
|
1019
|
+
// ── Multi-server (delegated to plugin) ────────────────────────────────────
|
|
1020
|
+
getAvailableAuthServers() {
|
|
1021
|
+
return this.plugin.getAvailableServers?.() ?? [];
|
|
1389
1022
|
}
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
*/
|
|
1393
|
-
getUserName() {
|
|
1394
|
-
return this.userSubject.value?.display_name || '';
|
|
1023
|
+
getActiveAuthServer() {
|
|
1024
|
+
return this.plugin.getActiveServer?.() ?? null;
|
|
1395
1025
|
}
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1026
|
+
switchAuthServer(serverName) {
|
|
1027
|
+
if (!this.plugin.switchServer) {
|
|
1028
|
+
throw new Error('Multi-server mode not supported by the configured auth plugin');
|
|
1029
|
+
}
|
|
1030
|
+
this.plugin.switchServer(serverName);
|
|
1401
1031
|
}
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
*/
|
|
1405
|
-
getDisplayName() {
|
|
1406
|
-
return this.userSubject.value?.display_name || '';
|
|
1032
|
+
getAuthServerConfig(serverName) {
|
|
1033
|
+
return this.plugin.getServerConfig?.(serverName) ?? null;
|
|
1407
1034
|
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1035
|
+
isMultiServerMode() {
|
|
1036
|
+
return (this.plugin.getAvailableServers?.() ?? []).length > 0;
|
|
1037
|
+
}
|
|
1038
|
+
// ── Backward compatibility ────────────────────────────────────────────────
|
|
1039
|
+
/** @deprecated Use getCurrentUser()?.user_id instead */
|
|
1040
|
+
getUserId() { return this.userSubject.value?.user_id || 0; }
|
|
1041
|
+
/** @deprecated Use getCurrentUser()?.display_name instead */
|
|
1042
|
+
getUserName() { return this.userSubject.value?.display_name || ''; }
|
|
1043
|
+
/** @deprecated Use getCurrentUser()?.photo_url instead */
|
|
1044
|
+
getPhotoUrl() { return this.userSubject.value?.photo_url || ''; }
|
|
1045
|
+
/** @deprecated Use getCurrentUser()?.display_name instead */
|
|
1046
|
+
getDisplayName() { return this.userSubject.value?.display_name || ''; }
|
|
1047
|
+
/** @deprecated Use `/profile/${getCurrentUser()?.user_id}` instead */
|
|
1411
1048
|
getProfileUrl() {
|
|
1412
1049
|
const userId = this.userSubject.value?.user_id;
|
|
1413
1050
|
return userId ? `/profile/${userId}` : '';
|
|
1414
1051
|
}
|
|
1415
|
-
/**
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
async signin() {
|
|
1419
|
-
return this.isAuthenticated();
|
|
1420
|
-
}
|
|
1421
|
-
/**
|
|
1422
|
-
* @deprecated Use loginWithEmail() instead
|
|
1423
|
-
*/
|
|
1052
|
+
/** @deprecated Use isAuthenticated() instead */
|
|
1053
|
+
async signin() { return this.isAuthenticated(); }
|
|
1054
|
+
/** @deprecated Use loginWithEmail() instead */
|
|
1424
1055
|
async verifyCredentials(email, password) {
|
|
1425
1056
|
const result = await this.loginWithEmail(email, password);
|
|
1426
1057
|
return result.success;
|
|
1427
1058
|
}
|
|
1428
|
-
/**
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
}
|
|
1434
|
-
/**
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1059
|
+
/** @deprecated Check user.is_email_verified from getCurrentUser() instead */
|
|
1060
|
+
isSigninEmailValid() { return this.userSubject.value?.is_email_verified || false; }
|
|
1061
|
+
/** @deprecated No longer needed */
|
|
1062
|
+
onDialogClose() { }
|
|
1063
|
+
/** @deprecated No longer needed */
|
|
1064
|
+
closeSocialAuthDialog() { }
|
|
1065
|
+
/** @deprecated Use checkEmail() from the plugin directly */
|
|
1066
|
+
async getUserProfile(email, serverName) {
|
|
1067
|
+
if (!this.plugin.checkEmail)
|
|
1068
|
+
return null;
|
|
1069
|
+
const result = await this.plugin.checkEmail(email);
|
|
1070
|
+
return result.exists ? result.user : null;
|
|
1439
1071
|
}
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1072
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AuthService, deps: [{ token: AUTH_PLUGIN }, { token: TokenService }, { token: SigninStatusService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1073
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AuthService, providedIn: 'root' });
|
|
1074
|
+
}
|
|
1075
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AuthService, decorators: [{
|
|
1076
|
+
type: Injectable,
|
|
1077
|
+
args: [{
|
|
1078
|
+
providedIn: 'root'
|
|
1079
|
+
}]
|
|
1080
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
1081
|
+
type: Inject,
|
|
1082
|
+
args: [AUTH_PLUGIN]
|
|
1083
|
+
}] }, { type: TokenService }, { type: SigninStatusService }] });
|
|
1084
|
+
|
|
1085
|
+
class ApiConnectionService {
|
|
1086
|
+
tokens;
|
|
1087
|
+
signinStatus;
|
|
1088
|
+
environment;
|
|
1089
|
+
authService;
|
|
1090
|
+
host = ''; // base URL without trailing slash
|
|
1091
|
+
constructor(tokens, signinStatus, environment, authService) {
|
|
1092
|
+
this.tokens = tokens;
|
|
1093
|
+
this.signinStatus = signinStatus;
|
|
1094
|
+
this.environment = environment;
|
|
1095
|
+
this.authService = authService;
|
|
1096
|
+
this.host = environment.apiServer.host;
|
|
1445
1097
|
}
|
|
1446
|
-
|
|
1447
|
-
* @deprecated Check if user exists by calling /api/auth/check-email endpoint
|
|
1448
|
-
*/
|
|
1449
|
-
async getUserProfile(email, serverName) {
|
|
1098
|
+
async request(url, options, data) {
|
|
1450
1099
|
try {
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1100
|
+
if (data !== null) {
|
|
1101
|
+
const body = JSON.stringify(data);
|
|
1102
|
+
options.body = body || {};
|
|
1103
|
+
}
|
|
1104
|
+
const accessTokenIncluded = this.includeAccessToken(options);
|
|
1105
|
+
let response = await fetch(url, options);
|
|
1106
|
+
if (response.status === 401 && accessTokenIncluded) {
|
|
1107
|
+
response = await this.refreshAndRetry(url, options, response);
|
|
1108
|
+
}
|
|
1109
|
+
if (response.ok) {
|
|
1110
|
+
const json = await response.json();
|
|
1111
|
+
return new ApiResponse(json.status, json.data, json.message);
|
|
1112
|
+
}
|
|
1113
|
+
if (response.status === 401) {
|
|
1114
|
+
this.signinStatus.signedOut();
|
|
1115
|
+
}
|
|
1116
|
+
return this.handleError(response);
|
|
1459
1117
|
}
|
|
1460
1118
|
catch (error) {
|
|
1461
|
-
return
|
|
1119
|
+
return this.handleError(error);
|
|
1462
1120
|
}
|
|
1463
1121
|
}
|
|
1122
|
+
handleError(error) {
|
|
1123
|
+
console.error(`Backend returned code ${error.status}, full error: `, error);
|
|
1124
|
+
return new ApiResponse('error');
|
|
1125
|
+
}
|
|
1126
|
+
async get(endpoint, queryParamsObj) {
|
|
1127
|
+
const url = this.host + endpoint + this.buildQueryString(queryParamsObj);
|
|
1128
|
+
const fetchOptions = { mode: 'cors', redirect: 'error' };
|
|
1129
|
+
return this.request(url, fetchOptions, null);
|
|
1130
|
+
}
|
|
1131
|
+
async post(pathWithQueryParams, data) {
|
|
1132
|
+
const url = this.host + pathWithQueryParams;
|
|
1133
|
+
const fetchOptions = {
|
|
1134
|
+
method: 'POST',
|
|
1135
|
+
mode: 'cors',
|
|
1136
|
+
redirect: 'error',
|
|
1137
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1138
|
+
body: JSON.stringify(data)
|
|
1139
|
+
};
|
|
1140
|
+
return this.request(url, fetchOptions, data);
|
|
1141
|
+
}
|
|
1142
|
+
async put(pathWithQueryParams, data) {
|
|
1143
|
+
const url = this.host + pathWithQueryParams;
|
|
1144
|
+
const fetchOptions = {
|
|
1145
|
+
method: 'PUT',
|
|
1146
|
+
mode: 'cors',
|
|
1147
|
+
redirect: 'error',
|
|
1148
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1149
|
+
body: JSON.stringify(data)
|
|
1150
|
+
};
|
|
1151
|
+
return this.request(url, fetchOptions, data);
|
|
1152
|
+
}
|
|
1153
|
+
async patch(pathWithQueryParams, data) {
|
|
1154
|
+
const url = this.host + pathWithQueryParams;
|
|
1155
|
+
const fetchOptions = {
|
|
1156
|
+
method: 'PATCH',
|
|
1157
|
+
mode: 'cors',
|
|
1158
|
+
redirect: 'error',
|
|
1159
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1160
|
+
body: JSON.stringify(data)
|
|
1161
|
+
};
|
|
1162
|
+
return this.request(url, fetchOptions, data);
|
|
1163
|
+
}
|
|
1164
|
+
async delete(endpoint, queryParamsObj) {
|
|
1165
|
+
const url = this.host + endpoint + this.buildQueryString(queryParamsObj);
|
|
1166
|
+
const fetchOptions = { method: 'DELETE', mode: 'cors', redirect: 'error' };
|
|
1167
|
+
return this.request(url, fetchOptions, null);
|
|
1168
|
+
}
|
|
1169
|
+
includeAccessToken(options) {
|
|
1170
|
+
const accessToken = this.tokens.getAccessToken();
|
|
1171
|
+
if (!accessToken)
|
|
1172
|
+
return false;
|
|
1173
|
+
if (!options.headers)
|
|
1174
|
+
options.headers = {};
|
|
1175
|
+
options.headers['Authorization'] = 'Bearer ' + accessToken;
|
|
1176
|
+
return true;
|
|
1177
|
+
}
|
|
1178
|
+
async refreshAndRetry(url, fetchOptions, response) {
|
|
1179
|
+
const refreshed = await this.authService.refresh();
|
|
1180
|
+
if (!refreshed)
|
|
1181
|
+
return response;
|
|
1182
|
+
fetchOptions.headers['Authorization'] = 'Bearer ' + this.tokens.getAccessToken();
|
|
1183
|
+
return fetch(url, fetchOptions);
|
|
1184
|
+
}
|
|
1464
1185
|
/**
|
|
1465
|
-
*
|
|
1466
|
-
*
|
|
1467
|
-
* @param serverName - Optional: Specify which auth server to query (for multi-server mode)
|
|
1186
|
+
* Refresh the access token (delegates to AuthService → AuthPlugin).
|
|
1187
|
+
* Kept public for backward compatibility.
|
|
1468
1188
|
*/
|
|
1469
|
-
async
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
if (
|
|
1478
|
-
|
|
1189
|
+
async refreshAccessToken() {
|
|
1190
|
+
return this.authService.refresh();
|
|
1191
|
+
}
|
|
1192
|
+
buildQueryString(options) {
|
|
1193
|
+
if (options === undefined)
|
|
1194
|
+
return '';
|
|
1195
|
+
const array = [];
|
|
1196
|
+
for (const key in options) {
|
|
1197
|
+
if (options.hasOwnProperty(key) && options[key] !== null && options[key] !== undefined) {
|
|
1198
|
+
array.push(encodeURIComponent(key) + '=' + encodeURIComponent(options[key]));
|
|
1479
1199
|
}
|
|
1480
|
-
return await response.json();
|
|
1481
|
-
}
|
|
1482
|
-
catch (error) {
|
|
1483
|
-
throw error;
|
|
1484
1200
|
}
|
|
1201
|
+
const str = array.join('&');
|
|
1202
|
+
return str ? '?' + str : '';
|
|
1485
1203
|
}
|
|
1486
1204
|
/**
|
|
1487
|
-
*
|
|
1488
|
-
* @
|
|
1489
|
-
* @param tenantName - Tenant organization name
|
|
1490
|
-
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
1205
|
+
* Upload a drawing (uses upload server if configured, otherwise API server)
|
|
1206
|
+
* @deprecated Platform-specific method - consider moving to platform service
|
|
1491
1207
|
*/
|
|
1492
|
-
async
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
body: JSON.stringify({
|
|
1508
|
-
platform: this.environment.platformCode,
|
|
1509
|
-
tenant_name: tenantName,
|
|
1510
|
-
country_code: countryCode,
|
|
1511
|
-
provider: 'google', // Assuming OAuth
|
|
1512
|
-
oauth_token: accessToken
|
|
1513
|
-
})
|
|
1514
|
-
});
|
|
1515
|
-
if (!response.ok) {
|
|
1516
|
-
const errorData = await response.json();
|
|
1517
|
-
throw new Error(errorData.message || 'Failed to create tenant');
|
|
1518
|
-
}
|
|
1519
|
-
const data = await response.json();
|
|
1520
|
-
// Update tokens with new tenant-scoped tokens
|
|
1521
|
-
if (data.access_token) {
|
|
1522
|
-
this.tokens.setAccessToken(data.access_token);
|
|
1523
|
-
this.signinStatus.setSigninStatus(true);
|
|
1524
|
-
}
|
|
1525
|
-
return data;
|
|
1526
|
-
}
|
|
1527
|
-
catch (error) {
|
|
1528
|
-
throw error;
|
|
1529
|
-
}
|
|
1208
|
+
async uploadDrawing(formData) {
|
|
1209
|
+
const uploadHost = this.environment.uploadServer?.host || this.host;
|
|
1210
|
+
const url = uploadHost + '/upload/drawing';
|
|
1211
|
+
const fetchOptions = { method: 'POST', mode: 'cors', redirect: 'error', body: formData };
|
|
1212
|
+
return this.request(url, fetchOptions, null);
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Upload an image (uses upload server if configured, otherwise API server)
|
|
1216
|
+
* @deprecated Platform-specific method - consider moving to platform service
|
|
1217
|
+
*/
|
|
1218
|
+
async uploadImage(formData) {
|
|
1219
|
+
const uploadHost = this.environment.uploadServer?.host || this.host;
|
|
1220
|
+
const url = uploadHost + '/upload/image';
|
|
1221
|
+
const fetchOptions = { method: 'POST', mode: 'cors', redirect: 'error', body: formData };
|
|
1222
|
+
return this.request(url, fetchOptions, null);
|
|
1530
1223
|
}
|
|
1531
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1532
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
1224
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ApiConnectionService, deps: [{ token: TokenService }, { token: SigninStatusService }, { token: MyEnvironmentModel }, { token: AuthService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1225
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ApiConnectionService, providedIn: 'root' });
|
|
1533
1226
|
}
|
|
1534
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1227
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ApiConnectionService, decorators: [{
|
|
1535
1228
|
type: Injectable,
|
|
1536
1229
|
args: [{
|
|
1537
1230
|
providedIn: 'root'
|
|
1538
1231
|
}]
|
|
1539
|
-
}], ctorParameters: () => [{ type: TokenService }, { type: SigninStatusService }, { type: MyEnvironmentModel,
|
|
1540
|
-
type: Inject,
|
|
1541
|
-
args: [MyEnvironmentModel]
|
|
1542
|
-
}] }] });
|
|
1232
|
+
}], ctorParameters: () => [{ type: TokenService }, { type: SigninStatusService }, { type: MyEnvironmentModel }, { type: AuthService }] });
|
|
1543
1233
|
|
|
1544
1234
|
class DbService {
|
|
1545
1235
|
constructor() { }
|
|
1546
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1547
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
1236
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DbService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1237
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DbService, providedIn: 'root' });
|
|
1548
1238
|
}
|
|
1549
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1239
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DbService, decorators: [{
|
|
1550
1240
|
type: Injectable,
|
|
1551
1241
|
args: [{
|
|
1552
1242
|
providedIn: 'root'
|
|
1553
1243
|
}]
|
|
1554
1244
|
}], ctorParameters: () => [] });
|
|
1555
1245
|
|
|
1246
|
+
/**
|
|
1247
|
+
* CSRF Token Service
|
|
1248
|
+
*
|
|
1249
|
+
* Manages CSRF tokens for cookie-based authentication.
|
|
1250
|
+
* Reads CSRF token from cookies and provides it for request headers.
|
|
1251
|
+
*/
|
|
1252
|
+
class CsrfService {
|
|
1253
|
+
/**
|
|
1254
|
+
* Get CSRF token from cookie
|
|
1255
|
+
*/
|
|
1256
|
+
getCsrfToken(cookieName = 'csrf_token') {
|
|
1257
|
+
const cookies = document.cookie.split(';');
|
|
1258
|
+
for (const cookie of cookies) {
|
|
1259
|
+
const [name, value] = cookie.trim().split('=');
|
|
1260
|
+
if (name === cookieName) {
|
|
1261
|
+
return decodeURIComponent(value);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
return null;
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Check if CSRF token exists
|
|
1268
|
+
*/
|
|
1269
|
+
hasCsrfToken(cookieName = 'csrf_token') {
|
|
1270
|
+
return this.getCsrfToken(cookieName) !== null;
|
|
1271
|
+
}
|
|
1272
|
+
/**
|
|
1273
|
+
* Clear CSRF token (for logout)
|
|
1274
|
+
* Note: Client-side deletion is limited for httpOnly cookies
|
|
1275
|
+
*/
|
|
1276
|
+
clearCsrfToken(cookieName = 'csrf_token') {
|
|
1277
|
+
// Can only clear non-httpOnly cookies
|
|
1278
|
+
document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
|
|
1279
|
+
}
|
|
1280
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CsrfService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1281
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CsrfService, providedIn: 'root' });
|
|
1282
|
+
}
|
|
1283
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CsrfService, decorators: [{
|
|
1284
|
+
type: Injectable,
|
|
1285
|
+
args: [{
|
|
1286
|
+
providedIn: 'root'
|
|
1287
|
+
}]
|
|
1288
|
+
}] });
|
|
1289
|
+
|
|
1556
1290
|
/**
|
|
1557
1291
|
* Service for interacting with the stonescriptphp-files server.
|
|
1558
1292
|
* Handles file upload, download, list, and delete operations
|
|
@@ -1807,10 +1541,10 @@ class FilesService {
|
|
|
1807
1541
|
return false;
|
|
1808
1542
|
}
|
|
1809
1543
|
}
|
|
1810
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1811
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
1544
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: FilesService, deps: [{ token: TokenService }, { token: SigninStatusService }, { token: MyEnvironmentModel }, { token: CsrfService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1545
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: FilesService, providedIn: 'root' });
|
|
1812
1546
|
}
|
|
1813
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1547
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: FilesService, decorators: [{
|
|
1814
1548
|
type: Injectable,
|
|
1815
1549
|
args: [{
|
|
1816
1550
|
providedIn: 'root'
|
|
@@ -1986,10 +1720,10 @@ class ProviderRegistryService {
|
|
|
1986
1720
|
}
|
|
1987
1721
|
return Object.keys(styles).length > 0 ? styles : null;
|
|
1988
1722
|
}
|
|
1989
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1990
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
1723
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ProviderRegistryService, deps: [{ token: MyEnvironmentModel }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1724
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ProviderRegistryService, providedIn: 'root' });
|
|
1991
1725
|
}
|
|
1992
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1726
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ProviderRegistryService, decorators: [{
|
|
1993
1727
|
type: Injectable,
|
|
1994
1728
|
args: [{
|
|
1995
1729
|
providedIn: 'root'
|
|
@@ -1999,29 +1733,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
1999
1733
|
args: [MyEnvironmentModel]
|
|
2000
1734
|
}] }] });
|
|
2001
1735
|
|
|
2002
|
-
class NgxStoneScriptPhpClientModule {
|
|
2003
|
-
static forRoot(environment) {
|
|
2004
|
-
return {
|
|
2005
|
-
ngModule: NgxStoneScriptPhpClientModule,
|
|
2006
|
-
providers: [
|
|
2007
|
-
{ provide: MyEnvironmentModel, useValue: environment }
|
|
2008
|
-
]
|
|
2009
|
-
};
|
|
2010
|
-
}
|
|
2011
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: NgxStoneScriptPhpClientModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
2012
|
-
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: NgxStoneScriptPhpClientModule, imports: [CommonModule] });
|
|
2013
|
-
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: NgxStoneScriptPhpClientModule, imports: [CommonModule] });
|
|
2014
|
-
}
|
|
2015
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: NgxStoneScriptPhpClientModule, decorators: [{
|
|
2016
|
-
type: NgModule,
|
|
2017
|
-
args: [{
|
|
2018
|
-
declarations: [],
|
|
2019
|
-
imports: [
|
|
2020
|
-
CommonModule
|
|
2021
|
-
]
|
|
2022
|
-
}]
|
|
2023
|
-
}] });
|
|
2024
|
-
|
|
2025
1736
|
class TenantLoginComponent {
|
|
2026
1737
|
auth;
|
|
2027
1738
|
providerRegistry;
|
|
@@ -2249,8 +1960,8 @@ class TenantLoginComponent {
|
|
|
2249
1960
|
event.preventDefault();
|
|
2250
1961
|
this.createTenant.emit();
|
|
2251
1962
|
}
|
|
2252
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
2253
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.
|
|
1963
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TenantLoginComponent, deps: [{ token: AuthService }, { token: ProviderRegistryService }], target: i0.ɵɵFactoryTarget.Component });
|
|
1964
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: TenantLoginComponent, isStandalone: true, selector: "lib-tenant-login", inputs: { title: "title", providers: "providers", showTenantSelector: "showTenantSelector", autoSelectSingleTenant: "autoSelectSingleTenant", prefillEmail: "prefillEmail", allowTenantCreation: "allowTenantCreation", tenantSelectorTitle: "tenantSelectorTitle", tenantSelectorDescription: "tenantSelectorDescription", continueButtonText: "continueButtonText", registerLinkText: "registerLinkText", registerLinkAction: "registerLinkAction", createTenantLinkText: "createTenantLinkText", createTenantLinkAction: "createTenantLinkAction" }, outputs: { tenantSelected: "tenantSelected", createTenant: "createTenant" }, ngImport: i0, template: `
|
|
2254
1965
|
<div class="tenant-login-dialog">
|
|
2255
1966
|
@if (!showingTenantSelector) {
|
|
2256
1967
|
<!-- Step 1: Authentication -->
|
|
@@ -2418,7 +2129,7 @@ class TenantLoginComponent {
|
|
|
2418
2129
|
</div>
|
|
2419
2130
|
`, isInline: true, styles: [".tenant-login-dialog{padding:24px;max-width:450px;position:relative}.login-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.welcome-message{margin-bottom:16px;padding:12px;background:#e8f5e9;border-radius:4px;text-align:center;font-size:14px;color:#2e7d32}.selector-description{margin-bottom:20px;font-size:14px;color:#666;text-align:center}.email-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.divider{margin:16px 0;text-align:center;position:relative}.divider:before{content:\"\";position:absolute;top:50%;left:0;right:0;height:1px;background:#ddd}.divider span{background:#fff;padding:0 12px;position:relative;color:#666;font-size:12px}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.btn-zoho{background-color:#f0483e;color:#fff;border:1px solid #d63b32}.btn-zoho:hover{background-color:#d63b32}.oauth-icon{font-size:18px}.switch-method{margin-top:12px;text-align:center;font-size:14px}.switch-method a{color:#4285f4;text-decoration:none}.switch-method a:hover{text-decoration:underline}.tenant-list{margin-bottom:20px;display:flex;flex-direction:column;gap:12px}.tenant-item{display:flex;align-items:flex-start;gap:12px;padding:16px;border:2px solid #e0e0e0;border-radius:6px;cursor:pointer;transition:all .2s}.tenant-item:hover{border-color:#4285f4;background:#f8f9ff}.tenant-item.selected{border-color:#4285f4;background:#e8f0fe}.tenant-radio{flex-shrink:0;padding-top:2px}.tenant-radio input[type=radio]{width:18px;height:18px;cursor:pointer}.tenant-info{flex:1}.tenant-name{font-size:16px;font-weight:500;color:#333;margin-bottom:4px}.tenant-meta{font-size:13px;color:#666}.tenant-role{font-weight:500;color:#4285f4}.tenant-separator{margin:0 6px}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.register-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.register-link a{color:#4285f4;text-decoration:none}.register-link a:hover{text-decoration:underline}.create-tenant-link{margin-top:16px;padding-top:16px;border-top:1px solid #e0e0e0;text-align:center;font-size:14px;color:#666}.create-tenant-link a{color:#4285f4;text-decoration:none;font-weight:500}.create-tenant-link a:hover{text-decoration:underline}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i4.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i4.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
|
|
2420
2131
|
}
|
|
2421
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
2132
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TenantLoginComponent, decorators: [{
|
|
2422
2133
|
type: Component,
|
|
2423
2134
|
args: [{ selector: 'lib-tenant-login', standalone: true, imports: [CommonModule, FormsModule], template: `
|
|
2424
2135
|
<div class="tenant-login-dialog">
|
|
@@ -2723,8 +2434,8 @@ class RegisterComponent {
|
|
|
2723
2434
|
this.confirmPassword = '';
|
|
2724
2435
|
this.displayName = '';
|
|
2725
2436
|
}
|
|
2726
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
2727
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.
|
|
2437
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: RegisterComponent, deps: [{ token: AuthService }, { token: MyEnvironmentModel }], target: i0.ɵɵFactoryTarget.Component });
|
|
2438
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: RegisterComponent, isStandalone: true, selector: "lib-register", outputs: { navigateToLogin: "navigateToLogin" }, ngImport: i0, template: `
|
|
2728
2439
|
<div class="register-dialog">
|
|
2729
2440
|
<h2 class="register-title">Create Account</h2>
|
|
2730
2441
|
|
|
@@ -2853,7 +2564,7 @@ class RegisterComponent {
|
|
|
2853
2564
|
</div>
|
|
2854
2565
|
`, isInline: true, styles: [".register-dialog{padding:24px;max-width:400px;position:relative}.register-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.register-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-group label{display:block;margin-bottom:6px;font-size:14px;font-weight:500;color:#333}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:38px;background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.form-hint{display:block;margin-top:4px;font-size:12px;color:#666}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.success-message{margin-top:16px;padding:12px;background:#efe;color:#3a3;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.login-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.login-link a{color:#4285f4;text-decoration:none}.login-link a:hover{text-decoration:underline}.account-link-prompt{background:#f8f9fa;border:2px solid #4285f4;border-radius:8px;padding:24px;margin-bottom:16px;text-align:center}.prompt-icon{font-size:48px;margin-bottom:12px}.account-link-prompt h3{margin:0 0 12px;color:#333;font-size:20px;font-weight:500}.account-link-prompt p{margin:8px 0;color:#555;font-size:14px;line-height:1.6}.prompt-actions{margin-top:20px;display:flex;flex-direction:column;gap:10px}.btn-secondary{background:#fff;color:#333;border:1px solid #ddd}.btn-secondary:hover:not(:disabled){background:#f8f9fa;border-color:#ccc}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i4.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i4.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i4.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
|
|
2855
2566
|
}
|
|
2856
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
2567
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: RegisterComponent, decorators: [{
|
|
2857
2568
|
type: Component,
|
|
2858
2569
|
args: [{ selector: 'lib-register', standalone: true, imports: [CommonModule, FormsModule], template: `
|
|
2859
2570
|
<div class="register-dialog">
|
|
@@ -3039,8 +2750,8 @@ class AuthPageComponent {
|
|
|
3039
2750
|
(B < 255 ? B < 1 ? 0 : B : 255))
|
|
3040
2751
|
.toString(16).slice(1);
|
|
3041
2752
|
}
|
|
3042
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3043
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.
|
|
2753
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AuthPageComponent, deps: [{ token: MyEnvironmentModel }], target: i0.ɵɵFactoryTarget.Component });
|
|
2754
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: AuthPageComponent, isStandalone: true, selector: "lib-auth-page", inputs: { providers: "providers" }, outputs: { authenticated: "authenticated" }, ngImport: i0, template: `
|
|
3044
2755
|
<div class="auth-container" [style.background]="gradientStyle">
|
|
3045
2756
|
<div class="auth-card">
|
|
3046
2757
|
@if (logo) {
|
|
@@ -3067,7 +2778,7 @@ class AuthPageComponent {
|
|
|
3067
2778
|
</div>
|
|
3068
2779
|
`, isInline: true, styles: [".auth-container{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px;background:linear-gradient(135deg,#667eea,#764ba2)}.auth-card{background:#fff;border-radius:12px;box-shadow:0 10px 40px #0000001a;padding:40px;width:100%;max-width:480px}.logo{display:block;max-width:200px;max-height:80px;margin:0 auto 24px}.app-name{margin:0 0 12px;font-size:28px;font-weight:600;text-align:center;color:#1a202c}.subtitle{margin:0 0 32px;font-size:16px;text-align:center;color:#718096}:host ::ng-deep .tenant-login-dialog,:host ::ng-deep .register-dialog{padding:0;max-width:none}:host ::ng-deep .login-title,:host ::ng-deep .register-title{display:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: TenantLoginComponent, selector: "lib-tenant-login", inputs: ["title", "providers", "showTenantSelector", "autoSelectSingleTenant", "prefillEmail", "allowTenantCreation", "tenantSelectorTitle", "tenantSelectorDescription", "continueButtonText", "registerLinkText", "registerLinkAction", "createTenantLinkText", "createTenantLinkAction"], outputs: ["tenantSelected", "createTenant"] }, { kind: "component", type: RegisterComponent, selector: "lib-register", outputs: ["navigateToLogin"] }] });
|
|
3069
2780
|
}
|
|
3070
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
2781
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AuthPageComponent, decorators: [{
|
|
3071
2782
|
type: Component,
|
|
3072
2783
|
args: [{ selector: 'lib-auth-page', standalone: true, imports: [CommonModule, TenantLoginComponent, RegisterComponent], template: `
|
|
3073
2784
|
<div class="auth-container" [style.background]="gradientStyle">
|
|
@@ -3190,8 +2901,8 @@ class LoginDialogComponent {
|
|
|
3190
2901
|
// For now, just emit a console message
|
|
3191
2902
|
console.log('Register clicked - platform should handle navigation');
|
|
3192
2903
|
}
|
|
3193
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3194
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.
|
|
2904
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: LoginDialogComponent, deps: [{ token: AuthService }, { token: ProviderRegistryService }], target: i0.ɵɵFactoryTarget.Component });
|
|
2905
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: LoginDialogComponent, isStandalone: true, selector: "lib-login-dialog", inputs: { providers: "providers" }, ngImport: i0, template: `
|
|
3195
2906
|
<div class="login-dialog">
|
|
3196
2907
|
<h2 class="login-title">Sign In</h2>
|
|
3197
2908
|
|
|
@@ -3281,7 +2992,7 @@ class LoginDialogComponent {
|
|
|
3281
2992
|
</div>
|
|
3282
2993
|
`, isInline: true, styles: [".login-dialog{padding:24px;max-width:400px;position:relative}.login-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.email-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.divider{margin:16px 0;text-align:center;position:relative}.divider:before{content:\"\";position:absolute;top:50%;left:0;right:0;height:1px;background:#ddd}.divider span{background:#fff;padding:0 12px;position:relative;color:#666;font-size:12px}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.oauth-icon{font-size:18px}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.register-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.register-link a{color:#4285f4;text-decoration:none}.register-link a:hover{text-decoration:underline}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i4.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i4.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
|
|
3283
2994
|
}
|
|
3284
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
2995
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: LoginDialogComponent, decorators: [{
|
|
3285
2996
|
type: Component,
|
|
3286
2997
|
args: [{ selector: 'lib-login-dialog', standalone: true, imports: [CommonModule, FormsModule], template: `
|
|
3287
2998
|
<div class="login-dialog">
|
|
@@ -3536,7 +3247,6 @@ class TenantRegisterComponent {
|
|
|
3536
3247
|
try {
|
|
3537
3248
|
const result = await this.auth.registerTenant({
|
|
3538
3249
|
tenantName: this.tenantName,
|
|
3539
|
-
tenantSlug: this.tenantSlug,
|
|
3540
3250
|
provider: provider
|
|
3541
3251
|
});
|
|
3542
3252
|
if (result.success && result.tenant && result.user) {
|
|
@@ -3569,7 +3279,6 @@ class TenantRegisterComponent {
|
|
|
3569
3279
|
try {
|
|
3570
3280
|
const result = await this.auth.registerTenant({
|
|
3571
3281
|
tenantName: this.tenantName,
|
|
3572
|
-
tenantSlug: this.tenantSlug,
|
|
3573
3282
|
displayName: this.displayName,
|
|
3574
3283
|
email: this.email,
|
|
3575
3284
|
password: this.password,
|
|
@@ -3597,8 +3306,8 @@ class TenantRegisterComponent {
|
|
|
3597
3306
|
event.preventDefault();
|
|
3598
3307
|
this.navigateToLogin.emit();
|
|
3599
3308
|
}
|
|
3600
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3601
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.
|
|
3309
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TenantRegisterComponent, deps: [{ token: AuthService }, { token: ProviderRegistryService }], target: i0.ɵɵFactoryTarget.Component });
|
|
3310
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: TenantRegisterComponent, isStandalone: true, selector: "lib-tenant-register", inputs: { title: "title", providers: "providers", requireTenantName: "requireTenantName", tenantSectionTitle: "tenantSectionTitle", tenantNameLabel: "tenantNameLabel", tenantNamePlaceholder: "tenantNamePlaceholder", tenantSlugLabel: "tenantSlugLabel", tenantSlugPlaceholder: "tenantSlugPlaceholder", urlPreviewEnabled: "urlPreviewEnabled", urlPreviewPrefix: "urlPreviewPrefix", userSectionTitle: "userSectionTitle", oauthDescription: "oauthDescription", ownershipTitle: "ownershipTitle", ownershipMessage: "ownershipMessage", submitButtonText: "submitButtonText", loginLinkText: "loginLinkText", loginLinkAction: "loginLinkAction" }, outputs: { tenantCreated: "tenantCreated", navigateToLogin: "navigateToLogin" }, ngImport: i0, template: `
|
|
3602
3311
|
<div class="tenant-register-dialog">
|
|
3603
3312
|
<h2 class="register-title">{{ title }}</h2>
|
|
3604
3313
|
|
|
@@ -3808,7 +3517,7 @@ class TenantRegisterComponent {
|
|
|
3808
3517
|
</div>
|
|
3809
3518
|
`, isInline: true, styles: [".tenant-register-dialog{padding:24px;max-width:500px;position:relative}.register-title{margin:0 0 20px;font-size:24px;font-weight:500;text-align:center}.warning-box{display:flex;gap:12px;padding:16px;background:#fff3cd;border:1px solid #ffc107;border-radius:6px;margin-bottom:24px}.warning-icon{font-size:24px;line-height:1}.warning-content{flex:1}.warning-content strong{display:block;margin-bottom:4px;color:#856404;font-size:14px}.warning-content p{margin:0;color:#856404;font-size:13px;line-height:1.5}.section-header{font-size:16px;font-weight:600;margin:20px 0 12px;padding-bottom:8px;border-bottom:2px solid #e0e0e0;color:#333}.register-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-group label{display:block;margin-bottom:6px;font-size:14px;font-weight:500;color:#333}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box;transition:border-color .2s}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:38px;background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.form-control.input-error{border-color:#dc3545}.form-control.input-success{border-color:#28a745}.form-hint{display:block;margin-top:4px;font-size:12px;color:#666}.form-hint .checking{color:#666}.form-hint .available{color:#28a745;font-weight:500}.form-hint .error-text{color:#dc3545}.oauth-section{margin:16px 0}.oauth-description{margin-bottom:12px;font-size:14px;color:#666;text-align:center}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s,opacity .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.oauth-icon{font-size:18px}.switch-method{margin-top:12px;text-align:center;font-size:14px}.switch-method a{color:#4285f4;text-decoration:none}.switch-method a:hover{text-decoration:underline}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.success-message{margin-top:16px;padding:12px;background:#efe;color:#3a3;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffffff2;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.loading-text{margin:0;font-size:14px;color:#666}.login-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.login-link a{color:#4285f4;text-decoration:none}.login-link a:hover{text-decoration:underline}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i4.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i4.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i4.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i4.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
|
|
3810
3519
|
}
|
|
3811
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
3520
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TenantRegisterComponent, decorators: [{
|
|
3812
3521
|
type: Component,
|
|
3813
3522
|
args: [{ selector: 'lib-tenant-register', standalone: true, imports: [CommonModule, FormsModule], template: `
|
|
3814
3523
|
<div class="tenant-register-dialog">
|
|
@@ -4108,8 +3817,8 @@ class TenantLoginDialogComponent {
|
|
|
4108
3817
|
this.dialogRef.close({ action: 'create_tenant' });
|
|
4109
3818
|
}
|
|
4110
3819
|
}
|
|
4111
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
4112
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.
|
|
3820
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TenantLoginDialogComponent, deps: [{ token: 'DIALOG_DATA', optional: true }, { token: 'DIALOG_REF', optional: true }], target: i0.ɵɵFactoryTarget.Component });
|
|
3821
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: TenantLoginDialogComponent, isStandalone: true, selector: "lib-tenant-login-dialog", ngImport: i0, template: `
|
|
4113
3822
|
<div class="dialog-wrapper">
|
|
4114
3823
|
<lib-tenant-login
|
|
4115
3824
|
[title]="data?.title || 'Sign In'"
|
|
@@ -4130,7 +3839,7 @@ class TenantLoginDialogComponent {
|
|
|
4130
3839
|
</div>
|
|
4131
3840
|
`, isInline: true, styles: [".dialog-wrapper{padding:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: TenantLoginComponent, selector: "lib-tenant-login", inputs: ["title", "providers", "showTenantSelector", "autoSelectSingleTenant", "prefillEmail", "allowTenantCreation", "tenantSelectorTitle", "tenantSelectorDescription", "continueButtonText", "registerLinkText", "registerLinkAction", "createTenantLinkText", "createTenantLinkAction"], outputs: ["tenantSelected", "createTenant"] }] });
|
|
4132
3841
|
}
|
|
4133
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
3842
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TenantLoginDialogComponent, decorators: [{
|
|
4134
3843
|
type: Component,
|
|
4135
3844
|
args: [{ selector: 'lib-tenant-login-dialog', standalone: true, imports: [CommonModule, TenantLoginComponent], template: `
|
|
4136
3845
|
<div class="dialog-wrapper">
|
|
@@ -4211,8 +3920,8 @@ class TenantRegisterDialogComponent {
|
|
|
4211
3920
|
this.dialogRef.close({ action: 'navigate_to_login' });
|
|
4212
3921
|
}
|
|
4213
3922
|
}
|
|
4214
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
4215
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.
|
|
3923
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TenantRegisterDialogComponent, deps: [{ token: 'DIALOG_DATA', optional: true }, { token: 'DIALOG_REF', optional: true }], target: i0.ɵɵFactoryTarget.Component });
|
|
3924
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: TenantRegisterDialogComponent, isStandalone: true, selector: "lib-tenant-register-dialog", ngImport: i0, template: `
|
|
4216
3925
|
<div class="dialog-wrapper">
|
|
4217
3926
|
<lib-tenant-register
|
|
4218
3927
|
[title]="data?.title || 'Create New Organization'"
|
|
@@ -4238,7 +3947,7 @@ class TenantRegisterDialogComponent {
|
|
|
4238
3947
|
</div>
|
|
4239
3948
|
`, isInline: true, styles: [".dialog-wrapper{padding:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: TenantRegisterComponent, selector: "lib-tenant-register", inputs: ["title", "providers", "requireTenantName", "tenantSectionTitle", "tenantNameLabel", "tenantNamePlaceholder", "tenantSlugLabel", "tenantSlugPlaceholder", "urlPreviewEnabled", "urlPreviewPrefix", "userSectionTitle", "oauthDescription", "ownershipTitle", "ownershipMessage", "submitButtonText", "loginLinkText", "loginLinkAction"], outputs: ["tenantCreated", "navigateToLogin"] }] });
|
|
4240
3949
|
}
|
|
4241
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
3950
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TenantRegisterDialogComponent, decorators: [{
|
|
4242
3951
|
type: Component,
|
|
4243
3952
|
args: [{ selector: 'lib-tenant-register-dialog', standalone: true, imports: [CommonModule, TenantRegisterComponent], template: `
|
|
4244
3953
|
<div class="dialog-wrapper">
|
|
@@ -4280,10 +3989,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
4280
3989
|
/*
|
|
4281
3990
|
* Public API Surface of ngx-stonescriptphp-client
|
|
4282
3991
|
*/
|
|
3992
|
+
// ── Core setup ────────────────────────────────────────────────────────────────
|
|
4283
3993
|
|
|
4284
3994
|
/**
|
|
4285
3995
|
* Generated bundle index. Do not edit.
|
|
4286
3996
|
*/
|
|
4287
3997
|
|
|
4288
|
-
export { ApiConnectionService, ApiResponse, AuthPageComponent, AuthService, CsrfService, DbService, FilesService, LoginDialogComponent, MyEnvironmentModel,
|
|
3998
|
+
export { AUTH_PLUGIN, ApiConnectionService, ApiResponse, AuthPageComponent, AuthService, CsrfService, DbService, FilesService, LoginDialogComponent, MyEnvironmentModel, ProviderRegistryService, RegisterComponent, SigninStatusService, StoneScriptPHPAuth, TenantLoginComponent, TenantLoginDialogComponent, TenantRegisterComponent, TenantRegisterDialogComponent, TokenService, VerifyStatus, provideNgxStoneScriptPhpClient };
|
|
4289
3999
|
//# sourceMappingURL=progalaxyelabs-ngx-stonescriptphp-client.mjs.map
|