@progalaxyelabs/ngx-stonescriptphp-client 1.11.1 → 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 non-empty access token.
|
|
114
|
-
* Token is treated as opaque — validity is determined by the auth server.
|
|
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
|
-
/**
|
|
212
|
-
* Accounts/Authentication service server configuration
|
|
213
|
-
* Use this when auth is on a different server than the API
|
|
214
|
-
* Replaces the deprecated accountsUrl string with structured config
|
|
215
|
-
* @example { host: 'https://accounts.progalaxyelabs.com' }
|
|
216
|
-
*/
|
|
217
|
-
accountsServer;
|
|
218
24
|
/**
|
|
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 = {
|
|
@@ -234,667 +40,815 @@ class MyEnvironmentModel {
|
|
|
234
40
|
csrfHeaderName: 'X-CSRF-Token'
|
|
235
41
|
};
|
|
236
42
|
/**
|
|
237
|
-
*
|
|
238
|
-
* Register additional OAuth providers beyond the built-in ones
|
|
239
|
-
* (google, linkedin, apple, microsoft, github, zoho).
|
|
43
|
+
* Multiple authentication servers configuration (for StoneScriptPHPAuth multi-server mode).
|
|
240
44
|
* @example
|
|
241
45
|
* ```typescript
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
46
|
+
* authServers: {
|
|
47
|
+
* customer: { url: 'https://auth.progalaxyelabs.com', default: true },
|
|
48
|
+
* employee: { url: 'https://admin-auth.progalaxyelabs.com' }
|
|
245
49
|
* }
|
|
246
50
|
* ```
|
|
247
51
|
*/
|
|
248
|
-
|
|
52
|
+
authServers;
|
|
249
53
|
/**
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
* For external auth servers that return flat responses like
|
|
254
|
-
* { access_token, identity, ... }, override with:
|
|
54
|
+
* Custom OAuth provider configurations.
|
|
55
|
+
* @example
|
|
255
56
|
* ```typescript
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
* refreshTokenPath: 'refresh_token',
|
|
259
|
-
* userPath: 'identity',
|
|
260
|
-
* errorMessagePath: 'message'
|
|
57
|
+
* customProviders: {
|
|
58
|
+
* okta: { label: 'Sign in with Okta', cssClass: 'btn-okta', buttonStyle: { borderColor: '#007dc1' } }
|
|
261
59
|
* }
|
|
262
60
|
* ```
|
|
263
61
|
*/
|
|
264
|
-
|
|
62
|
+
customProviders;
|
|
265
63
|
/**
|
|
266
|
-
* Branding configuration for auth components
|
|
267
|
-
* Allows platforms to customize login/register pages without creating wrappers
|
|
64
|
+
* Branding configuration for auth UI components.
|
|
268
65
|
*/
|
|
269
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;
|
|
270
83
|
}
|
|
271
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
|
+
};
|
|
272
96
|
/**
|
|
273
|
-
*
|
|
97
|
+
* Built-in auth plugin for StoneScriptPHP backends.
|
|
274
98
|
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
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
|
|
277
108
|
*/
|
|
278
|
-
class
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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';
|
|
283
169
|
const cookies = document.cookie.split(';');
|
|
284
170
|
for (const cookie of cookies) {
|
|
285
171
|
const [name, value] = cookie.trim().split('=');
|
|
286
|
-
if (name === cookieName)
|
|
172
|
+
if (name === cookieName)
|
|
287
173
|
return decodeURIComponent(value);
|
|
288
|
-
}
|
|
289
174
|
}
|
|
290
175
|
return null;
|
|
291
176
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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;
|
|
297
189
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
* Note: Client-side deletion is limited for httpOnly cookies
|
|
301
|
-
*/
|
|
302
|
-
clearCsrfToken(cookieName = 'csrf_token') {
|
|
303
|
-
// Can only clear non-httpOnly cookies
|
|
304
|
-
document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
|
|
190
|
+
getPlatformApiUrl() {
|
|
191
|
+
return this.config.apiUrl || this.config.host;
|
|
305
192
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
providedIn: 'root'
|
|
313
|
-
}]
|
|
314
|
-
}] });
|
|
315
|
-
|
|
316
|
-
class ApiConnectionService {
|
|
317
|
-
tokens;
|
|
318
|
-
signinStatus;
|
|
319
|
-
environment;
|
|
320
|
-
csrf;
|
|
321
|
-
host = ''; // base URL without trailing slash
|
|
322
|
-
accessToken = '';
|
|
323
|
-
authConfig;
|
|
324
|
-
constructor(tokens, signinStatus, environment, csrf) {
|
|
325
|
-
this.tokens = tokens;
|
|
326
|
-
this.signinStatus = signinStatus;
|
|
327
|
-
this.environment = environment;
|
|
328
|
-
this.csrf = csrf;
|
|
329
|
-
this.host = environment.apiServer.host;
|
|
330
|
-
// Set default auth config based on mode
|
|
331
|
-
this.authConfig = {
|
|
332
|
-
mode: environment.auth?.mode || 'cookie',
|
|
333
|
-
refreshEndpoint: environment.auth?.refreshEndpoint,
|
|
334
|
-
useCsrf: environment.auth?.useCsrf,
|
|
335
|
-
refreshTokenCookieName: environment.auth?.refreshTokenCookieName || 'refresh_token',
|
|
336
|
-
csrfTokenCookieName: environment.auth?.csrfTokenCookieName || 'csrf_token',
|
|
337
|
-
csrfHeaderName: environment.auth?.csrfHeaderName || 'X-CSRF-Token'
|
|
338
|
-
};
|
|
339
|
-
// Set default refresh endpoint based on mode if not specified
|
|
340
|
-
if (!this.authConfig.refreshEndpoint) {
|
|
341
|
-
this.authConfig.refreshEndpoint = this.authConfig.mode === 'cookie'
|
|
342
|
-
? '/auth/refresh'
|
|
343
|
-
: '/user/refresh_access';
|
|
344
|
-
}
|
|
345
|
-
// Set default CSRF setting based on mode if not specified
|
|
346
|
-
if (this.authConfig.useCsrf === undefined) {
|
|
347
|
-
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;
|
|
348
199
|
}
|
|
200
|
+
return Object.keys(this.config.authServers)[0] || null;
|
|
349
201
|
}
|
|
350
|
-
|
|
202
|
+
restoreActiveServer() {
|
|
351
203
|
try {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
357
|
-
else {
|
|
358
|
-
options.body = {};
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
const accessTokenIncluded = this.includeAccessToken(options);
|
|
362
|
-
let response = await fetch(url, options);
|
|
363
|
-
if ((response.status === 401) && accessTokenIncluded) {
|
|
364
|
-
response = await this.refreshAccessTokenAndRetry(url, options, response);
|
|
365
|
-
}
|
|
366
|
-
if (response.ok) {
|
|
367
|
-
const json = await (response.json());
|
|
368
|
-
return (new ApiResponse(json.status, json.data, json.message));
|
|
369
|
-
}
|
|
370
|
-
if (response.status === 401) {
|
|
371
|
-
this.signinStatus.signedOut();
|
|
372
|
-
}
|
|
373
|
-
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();
|
|
374
208
|
}
|
|
375
|
-
catch
|
|
376
|
-
|
|
209
|
+
catch {
|
|
210
|
+
this.activeServer = this.getDefaultServer();
|
|
377
211
|
}
|
|
378
212
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
};
|
|
390
|
-
return this.request(url, fetchOptions, null);
|
|
391
|
-
}
|
|
392
|
-
async post(pathWithQueryParams, data) {
|
|
393
|
-
const url = this.host + pathWithQueryParams;
|
|
394
|
-
const fetchOptions = {
|
|
395
|
-
method: 'POST',
|
|
396
|
-
mode: 'cors',
|
|
397
|
-
redirect: 'error',
|
|
398
|
-
headers: {
|
|
399
|
-
'Content-Type': 'application/json'
|
|
400
|
-
},
|
|
401
|
-
body: JSON.stringify(data)
|
|
402
|
-
};
|
|
403
|
-
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;
|
|
404
223
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
const fetchOptions = {
|
|
408
|
-
method: 'PUT',
|
|
409
|
-
mode: 'cors',
|
|
410
|
-
redirect: 'error',
|
|
411
|
-
headers: {
|
|
412
|
-
'Content-Type': 'application/json'
|
|
413
|
-
},
|
|
414
|
-
body: JSON.stringify(data)
|
|
415
|
-
};
|
|
416
|
-
return this.request(url, fetchOptions, data);
|
|
224
|
+
getAvailableServers() {
|
|
225
|
+
return Object.keys(this.config.authServers ?? {});
|
|
417
226
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
const fetchOptions = {
|
|
421
|
-
method: 'PATCH',
|
|
422
|
-
mode: 'cors',
|
|
423
|
-
redirect: 'error',
|
|
424
|
-
headers: {
|
|
425
|
-
'Content-Type': 'application/json'
|
|
426
|
-
},
|
|
427
|
-
body: JSON.stringify(data)
|
|
428
|
-
};
|
|
429
|
-
return this.request(url, fetchOptions, data);
|
|
227
|
+
getActiveServer() {
|
|
228
|
+
return this.activeServer;
|
|
430
229
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
redirect: 'error'
|
|
437
|
-
};
|
|
438
|
-
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;
|
|
439
235
|
}
|
|
440
|
-
//
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
// }
|
|
461
|
-
// }
|
|
462
|
-
includeAccessToken(options) {
|
|
463
|
-
this.accessToken = this.tokens.getAccessToken();
|
|
464
|
-
if (!this.accessToken) {
|
|
465
|
-
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') };
|
|
466
256
|
}
|
|
467
|
-
|
|
468
|
-
|
|
257
|
+
catch {
|
|
258
|
+
return { success: false, message: 'Network error. Please try again.' };
|
|
469
259
|
}
|
|
470
|
-
options.headers['Authorization'] = 'Bearer ' + this.accessToken;
|
|
471
|
-
return true;
|
|
472
260
|
}
|
|
473
|
-
async
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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.' };
|
|
477
290
|
}
|
|
478
|
-
fetchOptions.headers['Authorization'] = 'Bearer ' + this.accessToken;
|
|
479
|
-
response = await fetch(url, fetchOptions);
|
|
480
|
-
return response;
|
|
481
291
|
}
|
|
482
|
-
async
|
|
483
|
-
|
|
484
|
-
|
|
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
|
+
});
|
|
485
301
|
}
|
|
486
|
-
|
|
487
|
-
|
|
302
|
+
catch (error) {
|
|
303
|
+
console.error('Logout API call failed:', error);
|
|
488
304
|
}
|
|
489
|
-
|
|
490
|
-
|
|
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 };
|
|
491
324
|
}
|
|
492
325
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
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() {
|
|
497
335
|
try {
|
|
498
|
-
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
const headers = {
|
|
504
|
-
'Content-Type': 'application/json'
|
|
505
|
-
};
|
|
506
|
-
// Add CSRF token if enabled
|
|
507
|
-
if (this.authConfig.useCsrf) {
|
|
508
|
-
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();
|
|
509
341
|
if (!csrfToken) {
|
|
510
342
|
console.error('CSRF token not found in cookie');
|
|
511
|
-
return
|
|
343
|
+
return null;
|
|
512
344
|
}
|
|
513
|
-
headers[this.
|
|
345
|
+
headers[this.config.auth?.csrfHeaderName ?? 'X-CSRF-Token'] = csrfToken;
|
|
514
346
|
}
|
|
515
|
-
|
|
347
|
+
const response = await fetch(`${authHost}${endpoint}`, {
|
|
516
348
|
method: 'POST',
|
|
517
349
|
mode: 'cors',
|
|
518
|
-
credentials: 'include',
|
|
350
|
+
credentials: 'include',
|
|
519
351
|
redirect: 'error',
|
|
520
|
-
headers
|
|
352
|
+
headers
|
|
521
353
|
});
|
|
522
|
-
if (!
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
if (!refreshAccessData || refreshAccessData.status !== 'ok') {
|
|
529
|
-
return false;
|
|
530
|
-
}
|
|
531
|
-
// Extract access token from response
|
|
532
|
-
const newAccessToken = refreshAccessData.data?.access_token || refreshAccessData.access_token;
|
|
533
|
-
if (!newAccessToken) {
|
|
534
|
-
console.error('No access token in refresh response');
|
|
535
|
-
return false;
|
|
536
|
-
}
|
|
537
|
-
// Store new access token (refresh token is in httpOnly cookie)
|
|
538
|
-
this.tokens.setAccessToken(newAccessToken);
|
|
539
|
-
this.accessToken = newAccessToken;
|
|
540
|
-
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;
|
|
541
360
|
}
|
|
542
361
|
catch (error) {
|
|
543
362
|
console.error('Token refresh failed (cookie mode):', error);
|
|
544
|
-
|
|
545
|
-
this.tokens.clear();
|
|
546
|
-
return false;
|
|
363
|
+
return null;
|
|
547
364
|
}
|
|
548
365
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
async refreshAccessTokenBodyMode() {
|
|
366
|
+
async refreshBodyMode(accessToken, refreshToken) {
|
|
367
|
+
if (!refreshToken)
|
|
368
|
+
return null;
|
|
553
369
|
try {
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
}
|
|
558
|
-
// Determine auth server: accountsServer.host > accountsUrl > apiServer.host
|
|
559
|
-
const authHost = this.environment.accountsServer?.host
|
|
560
|
-
|| this.environment.accountsUrl
|
|
561
|
-
|| this.host;
|
|
562
|
-
const refreshTokenUrl = authHost + this.authConfig.refreshEndpoint;
|
|
563
|
-
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}`, {
|
|
564
373
|
method: 'POST',
|
|
565
374
|
mode: 'cors',
|
|
566
375
|
redirect: 'error',
|
|
567
|
-
headers: {
|
|
568
|
-
|
|
569
|
-
},
|
|
570
|
-
body: JSON.stringify({
|
|
571
|
-
access_token: this.accessToken,
|
|
572
|
-
refresh_token: refreshToken
|
|
573
|
-
})
|
|
376
|
+
headers: { 'Content-Type': 'application/json' },
|
|
377
|
+
body: JSON.stringify({ access_token: accessToken, refresh_token: refreshToken })
|
|
574
378
|
});
|
|
575
|
-
if (!
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
}
|
|
580
|
-
let refreshAccessData = await refreshTokenResponse.json();
|
|
581
|
-
if (!refreshAccessData) {
|
|
582
|
-
return false;
|
|
583
|
-
}
|
|
584
|
-
const newAccessToken = refreshAccessData.data?.access_token || refreshAccessData.access_token;
|
|
585
|
-
if (!newAccessToken) {
|
|
586
|
-
console.error('No access token in refresh response');
|
|
587
|
-
return false;
|
|
588
|
-
}
|
|
589
|
-
this.tokens.setTokens(newAccessToken, refreshToken);
|
|
590
|
-
this.accessToken = newAccessToken;
|
|
591
|
-
return true;
|
|
379
|
+
if (!response.ok)
|
|
380
|
+
return null;
|
|
381
|
+
const data = await response.json();
|
|
382
|
+
return this.resolveAccessToken(data) ?? null;
|
|
592
383
|
}
|
|
593
384
|
catch (error) {
|
|
594
385
|
console.error('Token refresh failed (body mode):', error);
|
|
595
|
-
|
|
596
|
-
this.tokens.clear();
|
|
597
|
-
return false;
|
|
386
|
+
return null;
|
|
598
387
|
}
|
|
599
388
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
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;
|
|
608
401
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
const url = uploadHost + '/upload/image';
|
|
638
|
-
const fetchOptions = {
|
|
639
|
-
method: 'POST',
|
|
640
|
-
mode: 'cors',
|
|
641
|
-
redirect: 'error',
|
|
642
|
-
body: formData
|
|
643
|
-
};
|
|
644
|
-
return this.request(url, fetchOptions, null);
|
|
645
|
-
}
|
|
646
|
-
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 });
|
|
647
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ApiConnectionService, providedIn: 'root' });
|
|
648
|
-
}
|
|
649
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ApiConnectionService, decorators: [{
|
|
650
|
-
type: Injectable,
|
|
651
|
-
args: [{
|
|
652
|
-
providedIn: 'root'
|
|
653
|
-
}]
|
|
654
|
-
}], ctorParameters: () => [{ type: TokenService }, { type: SigninStatusService }, { type: MyEnvironmentModel }, { type: CsrfService }] });
|
|
655
|
-
|
|
656
|
-
class AuthService {
|
|
657
|
-
tokens;
|
|
658
|
-
signinStatus;
|
|
659
|
-
environment;
|
|
660
|
-
USER_STORAGE_KEY = 'progalaxyapi_user';
|
|
661
|
-
ACTIVE_AUTH_SERVER_KEY = 'progalaxyapi_active_auth_server';
|
|
662
|
-
// Observable user state
|
|
663
|
-
userSubject = new BehaviorSubject(null);
|
|
664
|
-
user$ = this.userSubject.asObservable();
|
|
665
|
-
// Current active auth server name (for multi-server mode)
|
|
666
|
-
activeAuthServer = null;
|
|
667
|
-
constructor(tokens, signinStatus, environment) {
|
|
668
|
-
this.tokens = tokens;
|
|
669
|
-
this.signinStatus = signinStatus;
|
|
670
|
-
this.environment = environment;
|
|
671
|
-
// Restore user from localStorage on initialization
|
|
672
|
-
this.restoreUser();
|
|
673
|
-
// Restore active auth server
|
|
674
|
-
this.restoreActiveAuthServer();
|
|
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
|
+
});
|
|
675
430
|
}
|
|
676
|
-
//
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
}
|
|
689
|
-
const
|
|
690
|
-
if (
|
|
691
|
-
|
|
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) };
|
|
692
447
|
}
|
|
693
|
-
return
|
|
448
|
+
return { success: false, message: this.resolveErrorMessage(data, 'Failed to select tenant') };
|
|
694
449
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
return this.environment.accountsUrl;
|
|
450
|
+
catch {
|
|
451
|
+
return { success: false, message: 'Network error. Please try again.' };
|
|
698
452
|
}
|
|
699
|
-
throw new Error('No authentication server configured. Set either accountsUrl or authServers in environment config.');
|
|
700
453
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
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 || [];
|
|
707
467
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
if (config.default) {
|
|
711
|
-
return name;
|
|
712
|
-
}
|
|
468
|
+
catch {
|
|
469
|
+
return [];
|
|
713
470
|
}
|
|
714
|
-
// If no default is marked, use the first server
|
|
715
|
-
const firstServer = Object.keys(this.environment.authServers)[0];
|
|
716
|
-
return firstServer || null;
|
|
717
471
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
472
|
+
async registerTenant(data, accessToken) {
|
|
473
|
+
if (data.provider !== 'emailPassword') {
|
|
474
|
+
return this.registerTenantWithOAuth(data.tenantName, data.provider);
|
|
475
|
+
}
|
|
722
476
|
try {
|
|
723
|
-
const
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
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();
|
|
731
500
|
}
|
|
732
|
-
catch
|
|
733
|
-
|
|
734
|
-
this.activeAuthServer = this.getDefaultAuthServer();
|
|
501
|
+
catch {
|
|
502
|
+
return { success: false, message: 'Network error. Please try again.' };
|
|
735
503
|
}
|
|
736
504
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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;
|
|
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) {
|
|
741
545
|
try {
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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 };
|
|
747
553
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
* Get available auth servers
|
|
751
|
-
* @returns Array of server names or empty array if using single-server mode
|
|
752
|
-
*/
|
|
753
|
-
getAvailableAuthServers() {
|
|
754
|
-
if (!this.environment.authServers) {
|
|
755
|
-
return [];
|
|
554
|
+
catch {
|
|
555
|
+
return { available: true };
|
|
756
556
|
}
|
|
757
|
-
return Object.keys(this.environment.authServers);
|
|
758
557
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
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();
|
|
765
565
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
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');
|
|
777
586
|
}
|
|
778
|
-
|
|
587
|
+
return response.json();
|
|
779
588
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
589
|
+
async checkEmail(email) {
|
|
590
|
+
try {
|
|
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 };
|
|
787
599
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
return null;
|
|
600
|
+
catch {
|
|
601
|
+
return { exists: false };
|
|
791
602
|
}
|
|
792
|
-
return this.environment.authServers[targetServer] || null;
|
|
793
603
|
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
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';
|
|
799
666
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
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);
|
|
808
678
|
}
|
|
809
|
-
|
|
810
|
-
|
|
679
|
+
return this;
|
|
680
|
+
}
|
|
681
|
+
onNotOk(callback) {
|
|
682
|
+
if (this.status === 'not ok') {
|
|
683
|
+
callback(this.message, this.data);
|
|
811
684
|
}
|
|
812
|
-
|
|
685
|
+
return this;
|
|
813
686
|
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
successValue: 'ok',
|
|
820
|
-
accessTokenPath: 'data.access_token',
|
|
821
|
-
refreshTokenPath: 'data.refresh_token',
|
|
822
|
-
userPath: 'data.user',
|
|
823
|
-
errorMessagePath: 'message'
|
|
824
|
-
};
|
|
687
|
+
onError(callback) {
|
|
688
|
+
if (this.status === 'error') {
|
|
689
|
+
callback();
|
|
690
|
+
}
|
|
691
|
+
return this;
|
|
825
692
|
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
return path.split('.').reduce((o, key) => o?.[key], obj);
|
|
693
|
+
isSuccess() {
|
|
694
|
+
return this.status === 'ok';
|
|
829
695
|
}
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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 '';
|
|
835
743
|
}
|
|
836
|
-
// No successPath configured — success = access token present
|
|
837
|
-
return !!this.resolvePath(data, map.accessTokenPath);
|
|
838
744
|
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
745
|
+
getRefreshToken() {
|
|
746
|
+
if (this.refreshToken) {
|
|
747
|
+
return this.refreshToken;
|
|
748
|
+
}
|
|
749
|
+
const storedRefreshToken = localStorage.getItem(this.lsRefreshTokenKey);
|
|
750
|
+
if (storedRefreshToken) {
|
|
751
|
+
return storedRefreshToken;
|
|
752
|
+
}
|
|
753
|
+
else {
|
|
754
|
+
return '';
|
|
755
|
+
}
|
|
842
756
|
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
757
|
+
clear() {
|
|
758
|
+
this.accessToken = '';
|
|
759
|
+
this.refreshToken = '';
|
|
760
|
+
localStorage.removeItem(this.lsAccessTokenKey);
|
|
761
|
+
localStorage.removeItem(this.lsRefreshTokenKey);
|
|
846
762
|
}
|
|
847
|
-
/**
|
|
848
|
-
|
|
849
|
-
|
|
763
|
+
/**
|
|
764
|
+
* Check if there is a non-empty access token.
|
|
765
|
+
* Token is treated as opaque — validity is determined by the auth server.
|
|
766
|
+
*/
|
|
767
|
+
hasValidAccessToken() {
|
|
768
|
+
const token = this.getAccessToken();
|
|
769
|
+
return token !== null && token !== '';
|
|
850
770
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
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);
|
|
855
794
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
email: raw.email,
|
|
862
|
-
display_name: raw.display_name ?? raw.email?.split('@')[0] ?? '',
|
|
863
|
-
photo_url: raw.photo_url,
|
|
864
|
-
is_email_verified: raw.is_email_verified ?? false
|
|
865
|
-
};
|
|
795
|
+
signedOut() {
|
|
796
|
+
this.status.next(false);
|
|
797
|
+
}
|
|
798
|
+
signedIn() {
|
|
799
|
+
this.status.next(true);
|
|
866
800
|
}
|
|
867
801
|
/**
|
|
868
|
-
*
|
|
869
|
-
*
|
|
802
|
+
* Set signin status
|
|
803
|
+
* @param isSignedIn - True if user is signed in, false otherwise
|
|
870
804
|
*/
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
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();
|
|
879
840
|
}
|
|
880
|
-
|
|
881
|
-
* Restore user from localStorage
|
|
882
|
-
*/
|
|
841
|
+
// ── State management ──────────────────────────────────────────────────────
|
|
883
842
|
restoreUser() {
|
|
884
843
|
try {
|
|
885
844
|
const userJson = localStorage.getItem(this.USER_STORAGE_KEY);
|
|
886
|
-
if (userJson)
|
|
887
|
-
|
|
888
|
-
this.updateUser(user);
|
|
889
|
-
}
|
|
845
|
+
if (userJson)
|
|
846
|
+
this.updateUser(JSON.parse(userJson));
|
|
890
847
|
}
|
|
891
848
|
catch (error) {
|
|
892
849
|
console.error('Failed to restore user from localStorage:', error);
|
|
893
850
|
}
|
|
894
851
|
}
|
|
895
|
-
/**
|
|
896
|
-
* Save user to localStorage
|
|
897
|
-
*/
|
|
898
852
|
saveUser(user) {
|
|
899
853
|
try {
|
|
900
854
|
if (user) {
|
|
@@ -908,699 +862,431 @@ class AuthService {
|
|
|
908
862
|
console.error('Failed to save user to localStorage:', error);
|
|
909
863
|
}
|
|
910
864
|
}
|
|
911
|
-
/**
|
|
912
|
-
* Update user subject and persist to localStorage
|
|
913
|
-
*/
|
|
914
865
|
updateUser(user) {
|
|
915
866
|
this.userSubject.next(user);
|
|
916
867
|
this.saveUser(user);
|
|
917
868
|
}
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
password,
|
|
934
|
-
platform: this.environment.platformCode
|
|
935
|
-
})
|
|
936
|
-
});
|
|
937
|
-
const data = await response.json();
|
|
938
|
-
if (this.isAuthSuccess(data)) {
|
|
939
|
-
const accessToken = this.getAccessToken(data);
|
|
940
|
-
this.tokens.setAccessToken(accessToken);
|
|
941
|
-
this.signinStatus.setSigninStatus(true);
|
|
942
|
-
const rawUser = this.getUserFromResponse(data);
|
|
943
|
-
if (rawUser) {
|
|
944
|
-
const normalizedUser = this.normalizeUser(rawUser);
|
|
945
|
-
this.updateUser(normalizedUser);
|
|
946
|
-
return { success: true, user: normalizedUser };
|
|
947
|
-
}
|
|
948
|
-
return { success: true };
|
|
949
|
-
}
|
|
950
|
-
return {
|
|
951
|
-
success: false,
|
|
952
|
-
message: this.getErrorMessage(data, 'Invalid credentials')
|
|
953
|
-
};
|
|
954
|
-
}
|
|
955
|
-
catch (error) {
|
|
956
|
-
return {
|
|
957
|
-
success: false,
|
|
958
|
-
message: 'Network error. Please try again.'
|
|
959
|
-
};
|
|
960
|
-
}
|
|
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;
|
|
961
884
|
}
|
|
962
|
-
/**
|
|
963
|
-
* Login with Google OAuth (popup window)
|
|
964
|
-
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
965
|
-
*/
|
|
966
885
|
async loginWithGoogle(serverName) {
|
|
967
|
-
return this.
|
|
886
|
+
return this.loginWithProvider('google');
|
|
968
887
|
}
|
|
969
|
-
/**
|
|
970
|
-
* Login with GitHub OAuth (popup window)
|
|
971
|
-
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
972
|
-
*/
|
|
973
888
|
async loginWithGitHub(serverName) {
|
|
974
|
-
return this.
|
|
889
|
+
return this.loginWithProvider('github');
|
|
975
890
|
}
|
|
976
|
-
/**
|
|
977
|
-
* Login with LinkedIn OAuth (popup window)
|
|
978
|
-
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
979
|
-
*/
|
|
980
891
|
async loginWithLinkedIn(serverName) {
|
|
981
|
-
return this.
|
|
892
|
+
return this.loginWithProvider('linkedin');
|
|
982
893
|
}
|
|
983
|
-
/**
|
|
984
|
-
* Login with Apple OAuth (popup window)
|
|
985
|
-
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
986
|
-
*/
|
|
987
894
|
async loginWithApple(serverName) {
|
|
988
|
-
return this.
|
|
895
|
+
return this.loginWithProvider('apple');
|
|
989
896
|
}
|
|
990
|
-
/**
|
|
991
|
-
* Login with Microsoft OAuth (popup window)
|
|
992
|
-
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
993
|
-
*/
|
|
994
897
|
async loginWithMicrosoft(serverName) {
|
|
995
|
-
return this.
|
|
898
|
+
return this.loginWithProvider('microsoft');
|
|
996
899
|
}
|
|
997
|
-
/**
|
|
998
|
-
* Login with Zoho OAuth (popup window)
|
|
999
|
-
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
1000
|
-
*/
|
|
1001
900
|
async loginWithZoho(serverName) {
|
|
1002
|
-
return this.
|
|
901
|
+
return this.loginWithProvider('zoho');
|
|
1003
902
|
}
|
|
1004
|
-
/**
|
|
1005
|
-
* Generic provider-based login (supports all OAuth providers)
|
|
1006
|
-
* @param provider - The provider identifier
|
|
1007
|
-
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
1008
|
-
*/
|
|
1009
903
|
async loginWithProvider(provider, serverName) {
|
|
1010
904
|
if (provider === 'emailPassword') {
|
|
1011
905
|
throw new Error('Use loginWithEmail() for email/password authentication');
|
|
1012
906
|
}
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
*/
|
|
1021
|
-
async loginWithOAuth(provider, serverName) {
|
|
1022
|
-
return new Promise((resolve) => {
|
|
1023
|
-
const width = 500;
|
|
1024
|
-
const height = 600;
|
|
1025
|
-
const left = (window.screen.width - width) / 2;
|
|
1026
|
-
const top = (window.screen.height - height) / 2;
|
|
1027
|
-
const accountsUrl = this.getAccountsUrl(serverName);
|
|
1028
|
-
const oauthUrl = `${accountsUrl}/oauth/${provider}?` +
|
|
1029
|
-
`platform=${this.environment.platformCode}&` +
|
|
1030
|
-
`mode=popup`;
|
|
1031
|
-
const popup = window.open(oauthUrl, `${provider}_login`, `width=${width},height=${height},left=${left},top=${top}`);
|
|
1032
|
-
if (!popup) {
|
|
1033
|
-
resolve({
|
|
1034
|
-
success: false,
|
|
1035
|
-
message: 'Popup blocked. Please allow popups for this site.'
|
|
1036
|
-
});
|
|
1037
|
-
return;
|
|
1038
|
-
}
|
|
1039
|
-
// Listen for message from popup
|
|
1040
|
-
const messageHandler = (event) => {
|
|
1041
|
-
// Verify origin
|
|
1042
|
-
if (event.origin !== new URL(accountsUrl).origin) {
|
|
1043
|
-
return;
|
|
1044
|
-
}
|
|
1045
|
-
if (event.data.type === 'oauth_success') {
|
|
1046
|
-
this.tokens.setAccessToken(event.data.access_token);
|
|
1047
|
-
this.signinStatus.setSigninStatus(true);
|
|
1048
|
-
const rawUser = event.data.user || this.getUserFromResponse(event.data);
|
|
1049
|
-
const normalizedUser = rawUser ? this.normalizeUser(rawUser) : undefined;
|
|
1050
|
-
if (normalizedUser) {
|
|
1051
|
-
this.updateUser(normalizedUser);
|
|
1052
|
-
}
|
|
1053
|
-
window.removeEventListener('message', messageHandler);
|
|
1054
|
-
popup.close();
|
|
1055
|
-
resolve({
|
|
1056
|
-
success: true,
|
|
1057
|
-
user: normalizedUser
|
|
1058
|
-
});
|
|
1059
|
-
}
|
|
1060
|
-
else if (event.data.type === 'oauth_error') {
|
|
1061
|
-
window.removeEventListener('message', messageHandler);
|
|
1062
|
-
popup.close();
|
|
1063
|
-
resolve({
|
|
1064
|
-
success: false,
|
|
1065
|
-
message: event.data.message || 'OAuth login failed'
|
|
1066
|
-
});
|
|
1067
|
-
}
|
|
1068
|
-
};
|
|
1069
|
-
window.addEventListener('message', messageHandler);
|
|
1070
|
-
// Check if popup was closed manually
|
|
1071
|
-
const checkClosed = setInterval(() => {
|
|
1072
|
-
if (popup.closed) {
|
|
1073
|
-
clearInterval(checkClosed);
|
|
1074
|
-
window.removeEventListener('message', messageHandler);
|
|
1075
|
-
resolve({
|
|
1076
|
-
success: false,
|
|
1077
|
-
message: 'Login cancelled'
|
|
1078
|
-
});
|
|
1079
|
-
}
|
|
1080
|
-
}, 500);
|
|
1081
|
-
});
|
|
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;
|
|
1082
914
|
}
|
|
1083
|
-
/**
|
|
1084
|
-
* Register new user
|
|
1085
|
-
* @param email - User email
|
|
1086
|
-
* @param password - User password
|
|
1087
|
-
* @param displayName - Display name
|
|
1088
|
-
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
1089
|
-
*/
|
|
1090
915
|
async register(email, password, displayName, serverName) {
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1096
|
-
credentials: 'include',
|
|
1097
|
-
body: JSON.stringify({
|
|
1098
|
-
email,
|
|
1099
|
-
password,
|
|
1100
|
-
display_name: displayName,
|
|
1101
|
-
platform: this.environment.platformCode
|
|
1102
|
-
})
|
|
1103
|
-
});
|
|
1104
|
-
const data = await response.json();
|
|
1105
|
-
if (this.isAuthSuccess(data)) {
|
|
1106
|
-
const accessToken = this.getAccessToken(data);
|
|
1107
|
-
this.tokens.setAccessToken(accessToken);
|
|
1108
|
-
this.signinStatus.setSigninStatus(true);
|
|
1109
|
-
const rawUser = this.getUserFromResponse(data);
|
|
1110
|
-
if (rawUser) {
|
|
1111
|
-
const normalizedUser = this.normalizeUser(rawUser);
|
|
1112
|
-
this.updateUser(normalizedUser);
|
|
1113
|
-
return {
|
|
1114
|
-
success: true,
|
|
1115
|
-
user: normalizedUser,
|
|
1116
|
-
message: data.needs_verification ? 'Please verify your email' : undefined
|
|
1117
|
-
};
|
|
1118
|
-
}
|
|
1119
|
-
return { success: true };
|
|
1120
|
-
}
|
|
1121
|
-
return {
|
|
1122
|
-
success: false,
|
|
1123
|
-
message: this.getErrorMessage(data, 'Registration failed')
|
|
1124
|
-
};
|
|
1125
|
-
}
|
|
1126
|
-
catch (error) {
|
|
1127
|
-
return {
|
|
1128
|
-
success: false,
|
|
1129
|
-
message: 'Network error. Please try again.'
|
|
1130
|
-
};
|
|
1131
|
-
}
|
|
916
|
+
const result = await this.plugin.register(email, password, displayName);
|
|
917
|
+
if (result.success)
|
|
918
|
+
this.storeAuthResult(result);
|
|
919
|
+
return result;
|
|
1132
920
|
}
|
|
1133
|
-
/**
|
|
1134
|
-
* Sign out user
|
|
1135
|
-
* @param serverName - Optional: Specify which auth server to logout from (for multi-server mode)
|
|
1136
|
-
*/
|
|
1137
921
|
async signout(serverName) {
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
method: 'POST',
|
|
1144
|
-
headers: {
|
|
1145
|
-
'Content-Type': 'application/json'
|
|
1146
|
-
},
|
|
1147
|
-
credentials: 'include',
|
|
1148
|
-
body: JSON.stringify({
|
|
1149
|
-
refresh_token: refreshToken
|
|
1150
|
-
})
|
|
1151
|
-
});
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
catch (error) {
|
|
1155
|
-
console.error('Logout API call failed:', error);
|
|
1156
|
-
}
|
|
1157
|
-
finally {
|
|
1158
|
-
this.tokens.clear();
|
|
1159
|
-
this.signinStatus.setSigninStatus(false);
|
|
1160
|
-
this.updateUser(null);
|
|
1161
|
-
}
|
|
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);
|
|
1162
927
|
}
|
|
1163
|
-
/**
|
|
1164
|
-
* Check for active session (call on app init)
|
|
1165
|
-
* @param serverName - Optional: Specify which auth server to check (for multi-server mode)
|
|
1166
|
-
*/
|
|
1167
928
|
async checkSession(serverName) {
|
|
1168
929
|
if (this.tokens.hasValidAccessToken()) {
|
|
1169
930
|
this.signinStatus.setSigninStatus(true);
|
|
1170
931
|
return true;
|
|
1171
932
|
}
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
if (!response.ok) {
|
|
1180
|
-
// Refresh failed (expired, revoked, wrong keypair) — clear stale tokens
|
|
1181
|
-
// so the user gets a clean login page, not a broken retry loop
|
|
1182
|
-
this.tokens.clear();
|
|
1183
|
-
this.updateUser(null);
|
|
1184
|
-
this.signinStatus.setSigninStatus(false);
|
|
1185
|
-
return false;
|
|
1186
|
-
}
|
|
1187
|
-
const data = await response.json();
|
|
1188
|
-
const accessToken = this.getAccessToken(data) ?? data.access_token;
|
|
1189
|
-
if (accessToken) {
|
|
1190
|
-
this.tokens.setAccessToken(accessToken);
|
|
1191
|
-
const rawUser = this.getUserFromResponse(data) ?? data.user;
|
|
1192
|
-
if (rawUser) {
|
|
1193
|
-
this.updateUser(this.normalizeUser(rawUser));
|
|
1194
|
-
}
|
|
1195
|
-
this.signinStatus.setSigninStatus(true);
|
|
1196
|
-
return true;
|
|
1197
|
-
}
|
|
1198
|
-
// Response OK but no token — clear stale state
|
|
1199
|
-
this.tokens.clear();
|
|
1200
|
-
this.updateUser(null);
|
|
1201
|
-
return false;
|
|
1202
|
-
}
|
|
1203
|
-
catch (error) {
|
|
1204
|
-
// Network error — clear stale state to avoid retry loops
|
|
1205
|
-
this.tokens.clear();
|
|
1206
|
-
this.updateUser(null);
|
|
1207
|
-
this.signinStatus.setSigninStatus(false);
|
|
1208
|
-
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;
|
|
1209
940
|
}
|
|
941
|
+
this.tokens.clear();
|
|
942
|
+
this.updateUser(null);
|
|
943
|
+
this.signinStatus.setSigninStatus(false);
|
|
944
|
+
return false;
|
|
1210
945
|
}
|
|
1211
946
|
/**
|
|
1212
|
-
*
|
|
1213
|
-
|
|
1214
|
-
isAuthenticated() {
|
|
1215
|
-
return this.tokens.hasValidAccessToken();
|
|
1216
|
-
}
|
|
1217
|
-
/**
|
|
1218
|
-
* Get current user (synchronous)
|
|
1219
|
-
*/
|
|
1220
|
-
getCurrentUser() {
|
|
1221
|
-
return this.userSubject.value;
|
|
1222
|
-
}
|
|
1223
|
-
// ===== Multi-Tenant Methods =====
|
|
1224
|
-
/**
|
|
1225
|
-
* Register a new user AND create a new tenant (organization)
|
|
1226
|
-
* 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)
|
|
1227
949
|
*/
|
|
1228
|
-
async
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
}
|
|
1234
|
-
// Email/password registration — route through platform API proxy
|
|
1235
|
-
const apiUrl = this.getPlatformApiUrl();
|
|
1236
|
-
const response = await fetch(`${apiUrl}/auth/register-tenant`, {
|
|
1237
|
-
method: 'POST',
|
|
1238
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1239
|
-
credentials: 'include',
|
|
1240
|
-
body: JSON.stringify({
|
|
1241
|
-
platform: this.environment.platformCode,
|
|
1242
|
-
tenant_name: data.tenantName,
|
|
1243
|
-
tenant_slug: data.tenantSlug,
|
|
1244
|
-
display_name: data.displayName,
|
|
1245
|
-
email: data.email,
|
|
1246
|
-
password: data.password,
|
|
1247
|
-
provider: 'emailPassword'
|
|
1248
|
-
})
|
|
1249
|
-
});
|
|
1250
|
-
const result = await response.json();
|
|
1251
|
-
if (this.isAuthSuccess(result)) {
|
|
1252
|
-
const accessToken = this.getAccessToken(result);
|
|
1253
|
-
if (accessToken) {
|
|
1254
|
-
this.tokens.setAccessToken(accessToken);
|
|
1255
|
-
this.signinStatus.setSigninStatus(true);
|
|
1256
|
-
}
|
|
1257
|
-
const rawUser = this.getUserFromResponse(result);
|
|
1258
|
-
if (rawUser) {
|
|
1259
|
-
this.updateUser(this.normalizeUser(rawUser));
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
return result;
|
|
1263
|
-
}
|
|
1264
|
-
catch (error) {
|
|
1265
|
-
return {
|
|
1266
|
-
success: false,
|
|
1267
|
-
message: 'Network error. Please try again.'
|
|
1268
|
-
};
|
|
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;
|
|
1269
955
|
}
|
|
956
|
+
this.tokens.clear();
|
|
957
|
+
this.updateUser(null);
|
|
958
|
+
this.signinStatus.signedOut();
|
|
959
|
+
return false;
|
|
1270
960
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
* Opens popup window for OAuth flow
|
|
1274
|
-
*/
|
|
1275
|
-
async registerTenantWithOAuth(tenantName, tenantSlug, provider) {
|
|
1276
|
-
return new Promise((resolve) => {
|
|
1277
|
-
const width = 500;
|
|
1278
|
-
const height = 600;
|
|
1279
|
-
const left = (window.screen.width - width) / 2;
|
|
1280
|
-
const top = (window.screen.height - height) / 2;
|
|
1281
|
-
// Build OAuth URL with tenant registration params
|
|
1282
|
-
const accountsUrl = this.getAccountsUrl();
|
|
1283
|
-
const oauthUrl = `${accountsUrl}/oauth/${provider}?` +
|
|
1284
|
-
`platform=${this.environment.platformCode}&` +
|
|
1285
|
-
`mode=popup&` +
|
|
1286
|
-
`action=register_tenant&` +
|
|
1287
|
-
`tenant_name=${encodeURIComponent(tenantName)}&` +
|
|
1288
|
-
`tenant_slug=${encodeURIComponent(tenantSlug)}`;
|
|
1289
|
-
const popup = window.open(oauthUrl, `${provider}_register_tenant`, `width=${width},height=${height},left=${left},top=${top}`);
|
|
1290
|
-
if (!popup) {
|
|
1291
|
-
resolve({
|
|
1292
|
-
success: false,
|
|
1293
|
-
message: 'Popup blocked. Please allow popups for this site.'
|
|
1294
|
-
});
|
|
1295
|
-
return;
|
|
1296
|
-
}
|
|
1297
|
-
// Listen for message from popup
|
|
1298
|
-
const messageHandler = (event) => {
|
|
1299
|
-
// Verify origin
|
|
1300
|
-
if (event.origin !== new URL(accountsUrl).origin) {
|
|
1301
|
-
return;
|
|
1302
|
-
}
|
|
1303
|
-
if (event.data.type === 'tenant_register_success') {
|
|
1304
|
-
if (event.data.access_token) {
|
|
1305
|
-
this.tokens.setAccessToken(event.data.access_token);
|
|
1306
|
-
this.signinStatus.setSigninStatus(true);
|
|
1307
|
-
}
|
|
1308
|
-
const rawUser = event.data.user || this.getUserFromResponse(event.data);
|
|
1309
|
-
if (rawUser) {
|
|
1310
|
-
this.updateUser(this.normalizeUser(rawUser));
|
|
1311
|
-
}
|
|
1312
|
-
window.removeEventListener('message', messageHandler);
|
|
1313
|
-
popup.close();
|
|
1314
|
-
resolve({
|
|
1315
|
-
success: true,
|
|
1316
|
-
tenant: event.data.tenant,
|
|
1317
|
-
user: event.data.user
|
|
1318
|
-
});
|
|
1319
|
-
}
|
|
1320
|
-
else if (event.data.type === 'tenant_register_error') {
|
|
1321
|
-
window.removeEventListener('message', messageHandler);
|
|
1322
|
-
popup.close();
|
|
1323
|
-
resolve({
|
|
1324
|
-
success: false,
|
|
1325
|
-
message: event.data.message || 'Tenant registration failed'
|
|
1326
|
-
});
|
|
1327
|
-
}
|
|
1328
|
-
};
|
|
1329
|
-
window.addEventListener('message', messageHandler);
|
|
1330
|
-
// Check if popup was closed manually
|
|
1331
|
-
const checkClosed = setInterval(() => {
|
|
1332
|
-
if (popup.closed) {
|
|
1333
|
-
clearInterval(checkClosed);
|
|
1334
|
-
window.removeEventListener('message', messageHandler);
|
|
1335
|
-
resolve({
|
|
1336
|
-
success: false,
|
|
1337
|
-
message: 'Registration cancelled'
|
|
1338
|
-
});
|
|
1339
|
-
}
|
|
1340
|
-
}, 500);
|
|
1341
|
-
});
|
|
1342
|
-
}
|
|
1343
|
-
/**
|
|
1344
|
-
* Get all tenant memberships for the authenticated user
|
|
1345
|
-
* @param serverName - Optional: Specify which auth server to query (for multi-server mode)
|
|
1346
|
-
*/
|
|
1347
|
-
async getTenantMemberships(serverName) {
|
|
1348
|
-
try {
|
|
1349
|
-
const accountsUrl = this.getAccountsUrl(serverName);
|
|
1350
|
-
const response = await fetch(`${accountsUrl}/api/auth/memberships`, {
|
|
1351
|
-
method: 'GET',
|
|
1352
|
-
headers: {
|
|
1353
|
-
'Authorization': `Bearer ${this.tokens.getAccessToken()}`,
|
|
1354
|
-
'Content-Type': 'application/json'
|
|
1355
|
-
},
|
|
1356
|
-
credentials: 'include'
|
|
1357
|
-
});
|
|
1358
|
-
const data = await response.json();
|
|
1359
|
-
return {
|
|
1360
|
-
memberships: data.memberships || []
|
|
1361
|
-
};
|
|
1362
|
-
}
|
|
1363
|
-
catch (error) {
|
|
1364
|
-
return { memberships: [] };
|
|
1365
|
-
}
|
|
961
|
+
isAuthenticated() {
|
|
962
|
+
return this.tokens.hasValidAccessToken();
|
|
1366
963
|
}
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
try {
|
|
1375
|
-
const accountsUrl = this.getAccountsUrl(serverName);
|
|
1376
|
-
const response = await fetch(`${accountsUrl}/api/auth/select-tenant`, {
|
|
1377
|
-
method: 'POST',
|
|
1378
|
-
headers: {
|
|
1379
|
-
'Authorization': `Bearer ${this.tokens.getAccessToken()}`,
|
|
1380
|
-
'Content-Type': 'application/json'
|
|
1381
|
-
},
|
|
1382
|
-
credentials: 'include',
|
|
1383
|
-
body: JSON.stringify({ tenant_id: tenantId })
|
|
1384
|
-
});
|
|
1385
|
-
const data = await response.json();
|
|
1386
|
-
if (this.isAuthSuccess(data)) {
|
|
1387
|
-
const accessToken = this.getAccessToken(data);
|
|
1388
|
-
this.tokens.setAccessToken(accessToken);
|
|
1389
|
-
return {
|
|
1390
|
-
success: true,
|
|
1391
|
-
access_token: accessToken
|
|
1392
|
-
};
|
|
1393
|
-
}
|
|
1394
|
-
return {
|
|
1395
|
-
success: false,
|
|
1396
|
-
message: this.getErrorMessage(data, 'Failed to select tenant')
|
|
1397
|
-
};
|
|
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' };
|
|
1398
971
|
}
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
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);
|
|
1404
980
|
}
|
|
981
|
+
return result;
|
|
1405
982
|
}
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
method: 'GET',
|
|
1416
|
-
headers: { 'Content-Type': 'application/json' }
|
|
1417
|
-
});
|
|
1418
|
-
const data = await response.json();
|
|
1419
|
-
return {
|
|
1420
|
-
available: data.available || false,
|
|
1421
|
-
suggestion: data.suggestion
|
|
1422
|
-
};
|
|
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' };
|
|
1423
992
|
}
|
|
1424
|
-
|
|
1425
|
-
|
|
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)
|
|
1426
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);
|
|
1427
1016
|
}
|
|
1017
|
+
return result;
|
|
1428
1018
|
}
|
|
1429
|
-
//
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
/**
|
|
1433
|
-
* @deprecated Use getCurrentUser()?.user_id instead
|
|
1434
|
-
*/
|
|
1435
|
-
getUserId() {
|
|
1436
|
-
return this.userSubject.value?.user_id || 0;
|
|
1019
|
+
// ── Multi-server (delegated to plugin) ────────────────────────────────────
|
|
1020
|
+
getAvailableAuthServers() {
|
|
1021
|
+
return this.plugin.getAvailableServers?.() ?? [];
|
|
1437
1022
|
}
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
*/
|
|
1441
|
-
getUserName() {
|
|
1442
|
-
return this.userSubject.value?.display_name || '';
|
|
1023
|
+
getActiveAuthServer() {
|
|
1024
|
+
return this.plugin.getActiveServer?.() ?? null;
|
|
1443
1025
|
}
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
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);
|
|
1449
1031
|
}
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
*/
|
|
1453
|
-
getDisplayName() {
|
|
1454
|
-
return this.userSubject.value?.display_name || '';
|
|
1032
|
+
getAuthServerConfig(serverName) {
|
|
1033
|
+
return this.plugin.getServerConfig?.(serverName) ?? null;
|
|
1455
1034
|
}
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
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 */
|
|
1459
1048
|
getProfileUrl() {
|
|
1460
1049
|
const userId = this.userSubject.value?.user_id;
|
|
1461
1050
|
return userId ? `/profile/${userId}` : '';
|
|
1462
1051
|
}
|
|
1463
|
-
/**
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
async signin() {
|
|
1467
|
-
return this.isAuthenticated();
|
|
1468
|
-
}
|
|
1469
|
-
/**
|
|
1470
|
-
* @deprecated Use loginWithEmail() instead
|
|
1471
|
-
*/
|
|
1052
|
+
/** @deprecated Use isAuthenticated() instead */
|
|
1053
|
+
async signin() { return this.isAuthenticated(); }
|
|
1054
|
+
/** @deprecated Use loginWithEmail() instead */
|
|
1472
1055
|
async verifyCredentials(email, password) {
|
|
1473
1056
|
const result = await this.loginWithEmail(email, password);
|
|
1474
1057
|
return result.success;
|
|
1475
1058
|
}
|
|
1476
|
-
/**
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
}
|
|
1482
|
-
/**
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
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;
|
|
1487
1071
|
}
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
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;
|
|
1493
1097
|
}
|
|
1494
|
-
|
|
1495
|
-
* @deprecated Check if user exists by calling /api/auth/check-email endpoint
|
|
1496
|
-
*/
|
|
1497
|
-
async getUserProfile(email, serverName) {
|
|
1098
|
+
async request(url, options, data) {
|
|
1498
1099
|
try {
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
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);
|
|
1507
1117
|
}
|
|
1508
1118
|
catch (error) {
|
|
1509
|
-
return
|
|
1119
|
+
return this.handleError(error);
|
|
1510
1120
|
}
|
|
1511
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
|
+
}
|
|
1512
1185
|
/**
|
|
1513
|
-
*
|
|
1514
|
-
*
|
|
1515
|
-
* @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.
|
|
1516
1188
|
*/
|
|
1517
|
-
async
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
if (
|
|
1526
|
-
|
|
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]));
|
|
1527
1199
|
}
|
|
1528
|
-
return await response.json();
|
|
1529
|
-
}
|
|
1530
|
-
catch (error) {
|
|
1531
|
-
throw error;
|
|
1532
1200
|
}
|
|
1201
|
+
const str = array.join('&');
|
|
1202
|
+
return str ? '?' + str : '';
|
|
1533
1203
|
}
|
|
1534
1204
|
/**
|
|
1535
|
-
*
|
|
1536
|
-
* @
|
|
1537
|
-
* @param tenantName - Tenant organization name
|
|
1538
|
-
* @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
|
|
1539
1207
|
*/
|
|
1540
|
-
async
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
body: JSON.stringify({
|
|
1556
|
-
platform: this.environment.platformCode,
|
|
1557
|
-
tenant_name: tenantName,
|
|
1558
|
-
country_code: countryCode,
|
|
1559
|
-
provider: 'google', // Assuming OAuth
|
|
1560
|
-
oauth_token: accessToken
|
|
1561
|
-
})
|
|
1562
|
-
});
|
|
1563
|
-
if (!response.ok) {
|
|
1564
|
-
const errorData = await response.json();
|
|
1565
|
-
throw new Error(errorData.message || 'Failed to create tenant');
|
|
1566
|
-
}
|
|
1567
|
-
const data = await response.json();
|
|
1568
|
-
// Update tokens with new tenant-scoped tokens
|
|
1569
|
-
if (data.access_token) {
|
|
1570
|
-
this.tokens.setAccessToken(data.access_token);
|
|
1571
|
-
this.signinStatus.setSigninStatus(true);
|
|
1572
|
-
}
|
|
1573
|
-
return data;
|
|
1574
|
-
}
|
|
1575
|
-
catch (error) {
|
|
1576
|
-
throw error;
|
|
1577
|
-
}
|
|
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);
|
|
1578
1223
|
}
|
|
1579
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1580
|
-
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' });
|
|
1581
1226
|
}
|
|
1582
|
-
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: [{
|
|
1583
1228
|
type: Injectable,
|
|
1584
1229
|
args: [{
|
|
1585
1230
|
providedIn: 'root'
|
|
1586
1231
|
}]
|
|
1587
|
-
}], ctorParameters: () => [{ type: TokenService }, { type: SigninStatusService }, { type: MyEnvironmentModel,
|
|
1588
|
-
type: Inject,
|
|
1589
|
-
args: [MyEnvironmentModel]
|
|
1590
|
-
}] }] });
|
|
1232
|
+
}], ctorParameters: () => [{ type: TokenService }, { type: SigninStatusService }, { type: MyEnvironmentModel }, { type: AuthService }] });
|
|
1591
1233
|
|
|
1592
1234
|
class DbService {
|
|
1593
1235
|
constructor() { }
|
|
1594
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1595
|
-
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' });
|
|
1596
1238
|
}
|
|
1597
|
-
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: [{
|
|
1598
1240
|
type: Injectable,
|
|
1599
1241
|
args: [{
|
|
1600
1242
|
providedIn: 'root'
|
|
1601
1243
|
}]
|
|
1602
1244
|
}], ctorParameters: () => [] });
|
|
1603
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
|
+
|
|
1604
1290
|
/**
|
|
1605
1291
|
* Service for interacting with the stonescriptphp-files server.
|
|
1606
1292
|
* Handles file upload, download, list, and delete operations
|
|
@@ -1855,10 +1541,10 @@ class FilesService {
|
|
|
1855
1541
|
return false;
|
|
1856
1542
|
}
|
|
1857
1543
|
}
|
|
1858
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1859
|
-
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' });
|
|
1860
1546
|
}
|
|
1861
|
-
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: [{
|
|
1862
1548
|
type: Injectable,
|
|
1863
1549
|
args: [{
|
|
1864
1550
|
providedIn: 'root'
|
|
@@ -2034,10 +1720,10 @@ class ProviderRegistryService {
|
|
|
2034
1720
|
}
|
|
2035
1721
|
return Object.keys(styles).length > 0 ? styles : null;
|
|
2036
1722
|
}
|
|
2037
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
2038
|
-
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' });
|
|
2039
1725
|
}
|
|
2040
|
-
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: [{
|
|
2041
1727
|
type: Injectable,
|
|
2042
1728
|
args: [{
|
|
2043
1729
|
providedIn: 'root'
|
|
@@ -2047,29 +1733,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
2047
1733
|
args: [MyEnvironmentModel]
|
|
2048
1734
|
}] }] });
|
|
2049
1735
|
|
|
2050
|
-
class NgxStoneScriptPhpClientModule {
|
|
2051
|
-
static forRoot(environment) {
|
|
2052
|
-
return {
|
|
2053
|
-
ngModule: NgxStoneScriptPhpClientModule,
|
|
2054
|
-
providers: [
|
|
2055
|
-
{ provide: MyEnvironmentModel, useValue: environment }
|
|
2056
|
-
]
|
|
2057
|
-
};
|
|
2058
|
-
}
|
|
2059
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: NgxStoneScriptPhpClientModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
2060
|
-
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: NgxStoneScriptPhpClientModule, imports: [CommonModule] });
|
|
2061
|
-
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: NgxStoneScriptPhpClientModule, imports: [CommonModule] });
|
|
2062
|
-
}
|
|
2063
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: NgxStoneScriptPhpClientModule, decorators: [{
|
|
2064
|
-
type: NgModule,
|
|
2065
|
-
args: [{
|
|
2066
|
-
declarations: [],
|
|
2067
|
-
imports: [
|
|
2068
|
-
CommonModule
|
|
2069
|
-
]
|
|
2070
|
-
}]
|
|
2071
|
-
}] });
|
|
2072
|
-
|
|
2073
1736
|
class TenantLoginComponent {
|
|
2074
1737
|
auth;
|
|
2075
1738
|
providerRegistry;
|
|
@@ -2297,8 +1960,8 @@ class TenantLoginComponent {
|
|
|
2297
1960
|
event.preventDefault();
|
|
2298
1961
|
this.createTenant.emit();
|
|
2299
1962
|
}
|
|
2300
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
2301
|
-
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: `
|
|
2302
1965
|
<div class="tenant-login-dialog">
|
|
2303
1966
|
@if (!showingTenantSelector) {
|
|
2304
1967
|
<!-- Step 1: Authentication -->
|
|
@@ -2466,7 +2129,7 @@ class TenantLoginComponent {
|
|
|
2466
2129
|
</div>
|
|
2467
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"] }] });
|
|
2468
2131
|
}
|
|
2469
|
-
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: [{
|
|
2470
2133
|
type: Component,
|
|
2471
2134
|
args: [{ selector: 'lib-tenant-login', standalone: true, imports: [CommonModule, FormsModule], template: `
|
|
2472
2135
|
<div class="tenant-login-dialog">
|
|
@@ -2771,8 +2434,8 @@ class RegisterComponent {
|
|
|
2771
2434
|
this.confirmPassword = '';
|
|
2772
2435
|
this.displayName = '';
|
|
2773
2436
|
}
|
|
2774
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
2775
|
-
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: `
|
|
2776
2439
|
<div class="register-dialog">
|
|
2777
2440
|
<h2 class="register-title">Create Account</h2>
|
|
2778
2441
|
|
|
@@ -2901,7 +2564,7 @@ class RegisterComponent {
|
|
|
2901
2564
|
</div>
|
|
2902
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"] }] });
|
|
2903
2566
|
}
|
|
2904
|
-
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: [{
|
|
2905
2568
|
type: Component,
|
|
2906
2569
|
args: [{ selector: 'lib-register', standalone: true, imports: [CommonModule, FormsModule], template: `
|
|
2907
2570
|
<div class="register-dialog">
|
|
@@ -3087,8 +2750,8 @@ class AuthPageComponent {
|
|
|
3087
2750
|
(B < 255 ? B < 1 ? 0 : B : 255))
|
|
3088
2751
|
.toString(16).slice(1);
|
|
3089
2752
|
}
|
|
3090
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3091
|
-
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: `
|
|
3092
2755
|
<div class="auth-container" [style.background]="gradientStyle">
|
|
3093
2756
|
<div class="auth-card">
|
|
3094
2757
|
@if (logo) {
|
|
@@ -3115,7 +2778,7 @@ class AuthPageComponent {
|
|
|
3115
2778
|
</div>
|
|
3116
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"] }] });
|
|
3117
2780
|
}
|
|
3118
|
-
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: [{
|
|
3119
2782
|
type: Component,
|
|
3120
2783
|
args: [{ selector: 'lib-auth-page', standalone: true, imports: [CommonModule, TenantLoginComponent, RegisterComponent], template: `
|
|
3121
2784
|
<div class="auth-container" [style.background]="gradientStyle">
|
|
@@ -3238,8 +2901,8 @@ class LoginDialogComponent {
|
|
|
3238
2901
|
// For now, just emit a console message
|
|
3239
2902
|
console.log('Register clicked - platform should handle navigation');
|
|
3240
2903
|
}
|
|
3241
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3242
|
-
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: `
|
|
3243
2906
|
<div class="login-dialog">
|
|
3244
2907
|
<h2 class="login-title">Sign In</h2>
|
|
3245
2908
|
|
|
@@ -3329,7 +2992,7 @@ class LoginDialogComponent {
|
|
|
3329
2992
|
</div>
|
|
3330
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"] }] });
|
|
3331
2994
|
}
|
|
3332
|
-
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: [{
|
|
3333
2996
|
type: Component,
|
|
3334
2997
|
args: [{ selector: 'lib-login-dialog', standalone: true, imports: [CommonModule, FormsModule], template: `
|
|
3335
2998
|
<div class="login-dialog">
|
|
@@ -3584,7 +3247,6 @@ class TenantRegisterComponent {
|
|
|
3584
3247
|
try {
|
|
3585
3248
|
const result = await this.auth.registerTenant({
|
|
3586
3249
|
tenantName: this.tenantName,
|
|
3587
|
-
tenantSlug: this.tenantSlug,
|
|
3588
3250
|
provider: provider
|
|
3589
3251
|
});
|
|
3590
3252
|
if (result.success && result.tenant && result.user) {
|
|
@@ -3617,7 +3279,6 @@ class TenantRegisterComponent {
|
|
|
3617
3279
|
try {
|
|
3618
3280
|
const result = await this.auth.registerTenant({
|
|
3619
3281
|
tenantName: this.tenantName,
|
|
3620
|
-
tenantSlug: this.tenantSlug,
|
|
3621
3282
|
displayName: this.displayName,
|
|
3622
3283
|
email: this.email,
|
|
3623
3284
|
password: this.password,
|
|
@@ -3645,8 +3306,8 @@ class TenantRegisterComponent {
|
|
|
3645
3306
|
event.preventDefault();
|
|
3646
3307
|
this.navigateToLogin.emit();
|
|
3647
3308
|
}
|
|
3648
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3649
|
-
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: `
|
|
3650
3311
|
<div class="tenant-register-dialog">
|
|
3651
3312
|
<h2 class="register-title">{{ title }}</h2>
|
|
3652
3313
|
|
|
@@ -3856,7 +3517,7 @@ class TenantRegisterComponent {
|
|
|
3856
3517
|
</div>
|
|
3857
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"] }] });
|
|
3858
3519
|
}
|
|
3859
|
-
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: [{
|
|
3860
3521
|
type: Component,
|
|
3861
3522
|
args: [{ selector: 'lib-tenant-register', standalone: true, imports: [CommonModule, FormsModule], template: `
|
|
3862
3523
|
<div class="tenant-register-dialog">
|
|
@@ -4156,8 +3817,8 @@ class TenantLoginDialogComponent {
|
|
|
4156
3817
|
this.dialogRef.close({ action: 'create_tenant' });
|
|
4157
3818
|
}
|
|
4158
3819
|
}
|
|
4159
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
4160
|
-
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: `
|
|
4161
3822
|
<div class="dialog-wrapper">
|
|
4162
3823
|
<lib-tenant-login
|
|
4163
3824
|
[title]="data?.title || 'Sign In'"
|
|
@@ -4178,7 +3839,7 @@ class TenantLoginDialogComponent {
|
|
|
4178
3839
|
</div>
|
|
4179
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"] }] });
|
|
4180
3841
|
}
|
|
4181
|
-
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: [{
|
|
4182
3843
|
type: Component,
|
|
4183
3844
|
args: [{ selector: 'lib-tenant-login-dialog', standalone: true, imports: [CommonModule, TenantLoginComponent], template: `
|
|
4184
3845
|
<div class="dialog-wrapper">
|
|
@@ -4259,8 +3920,8 @@ class TenantRegisterDialogComponent {
|
|
|
4259
3920
|
this.dialogRef.close({ action: 'navigate_to_login' });
|
|
4260
3921
|
}
|
|
4261
3922
|
}
|
|
4262
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
4263
|
-
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: `
|
|
4264
3925
|
<div class="dialog-wrapper">
|
|
4265
3926
|
<lib-tenant-register
|
|
4266
3927
|
[title]="data?.title || 'Create New Organization'"
|
|
@@ -4286,7 +3947,7 @@ class TenantRegisterDialogComponent {
|
|
|
4286
3947
|
</div>
|
|
4287
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"] }] });
|
|
4288
3949
|
}
|
|
4289
|
-
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: [{
|
|
4290
3951
|
type: Component,
|
|
4291
3952
|
args: [{ selector: 'lib-tenant-register-dialog', standalone: true, imports: [CommonModule, TenantRegisterComponent], template: `
|
|
4292
3953
|
<div class="dialog-wrapper">
|
|
@@ -4328,10 +3989,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
4328
3989
|
/*
|
|
4329
3990
|
* Public API Surface of ngx-stonescriptphp-client
|
|
4330
3991
|
*/
|
|
3992
|
+
// ── Core setup ────────────────────────────────────────────────────────────────
|
|
4331
3993
|
|
|
4332
3994
|
/**
|
|
4333
3995
|
* Generated bundle index. Do not edit.
|
|
4334
3996
|
*/
|
|
4335
3997
|
|
|
4336
|
-
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 };
|
|
4337
3999
|
//# sourceMappingURL=progalaxyelabs-ngx-stonescriptphp-client.mjs.map
|