@progalaxyelabs/ngx-stonescriptphp-client 1.11.1 → 1.14.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,819 @@ 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) {
|
|
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
|
-
|
|
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: { 'Content-Type': 'application/json' },
|
|
493
|
+
credentials: 'include',
|
|
494
|
+
body: JSON.stringify(body)
|
|
495
|
+
});
|
|
496
|
+
const result = await response.json();
|
|
497
|
+
if (result?.status === 'ok' || result?.success === true) {
|
|
498
|
+
return { success: true };
|
|
730
499
|
}
|
|
500
|
+
return { success: false, message: result?.data?.message || result?.message || 'Registration failed' };
|
|
731
501
|
}
|
|
732
|
-
catch
|
|
733
|
-
|
|
734
|
-
this.activeAuthServer = this.getDefaultAuthServer();
|
|
502
|
+
catch {
|
|
503
|
+
return { success: false, message: 'Network error. Please try again.' };
|
|
735
504
|
}
|
|
736
505
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
506
|
+
async registerTenantWithOAuth(tenantName, provider) {
|
|
507
|
+
return new Promise((resolve) => {
|
|
508
|
+
const width = 500, height = 600;
|
|
509
|
+
const left = (window.screen.width - width) / 2;
|
|
510
|
+
const top = (window.screen.height - height) / 2;
|
|
511
|
+
const accountsUrl = this.getAccountsUrl();
|
|
512
|
+
const oauthUrl = `${accountsUrl}/oauth/${provider}?` +
|
|
513
|
+
`platform=${this.config.platformCode}&mode=popup&action=register_tenant&` +
|
|
514
|
+
`tenant_name=${encodeURIComponent(tenantName)}`;
|
|
515
|
+
const popup = window.open(oauthUrl, `${provider}_register_tenant`, `width=${width},height=${height},left=${left},top=${top}`);
|
|
516
|
+
if (!popup) {
|
|
517
|
+
resolve({ success: false, message: 'Popup blocked. Please allow popups for this site.' });
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
const messageHandler = (event) => {
|
|
521
|
+
if (event.origin !== new URL(accountsUrl).origin)
|
|
522
|
+
return;
|
|
523
|
+
if (event.data.type === 'tenant_register_success') {
|
|
524
|
+
window.removeEventListener('message', messageHandler);
|
|
525
|
+
popup.close();
|
|
526
|
+
resolve({
|
|
527
|
+
success: true,
|
|
528
|
+
accessToken: event.data.access_token,
|
|
529
|
+
user: event.data.user ? this.normalizeUser(event.data.user) : undefined
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
else if (event.data.type === 'tenant_register_error') {
|
|
533
|
+
window.removeEventListener('message', messageHandler);
|
|
534
|
+
popup.close();
|
|
535
|
+
resolve({ success: false, message: event.data.message || 'Tenant registration failed' });
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
window.addEventListener('message', messageHandler);
|
|
539
|
+
const checkClosed = setInterval(() => {
|
|
540
|
+
if (popup.closed) {
|
|
541
|
+
clearInterval(checkClosed);
|
|
542
|
+
window.removeEventListener('message', messageHandler);
|
|
543
|
+
resolve({ success: false, message: 'Registration cancelled' });
|
|
544
|
+
}
|
|
545
|
+
}, 500);
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
async checkTenantSlugAvailable(slug) {
|
|
741
549
|
try {
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
550
|
+
const accountsUrl = this.getAccountsUrl();
|
|
551
|
+
const response = await fetch(`${accountsUrl}/api/auth/check-tenant-slug/${slug}`, {
|
|
552
|
+
method: 'GET',
|
|
553
|
+
headers: { 'Content-Type': 'application/json' }
|
|
554
|
+
});
|
|
555
|
+
const data = await response.json();
|
|
556
|
+
return { available: data.available || false, suggestion: data.suggestion };
|
|
747
557
|
}
|
|
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 [];
|
|
558
|
+
catch {
|
|
559
|
+
return { available: true };
|
|
756
560
|
}
|
|
757
|
-
return Object.keys(this.environment.authServers);
|
|
758
561
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
562
|
+
async checkOnboardingStatus(identityId, platformCode) {
|
|
563
|
+
const accountsUrl = this.getAccountsUrl();
|
|
564
|
+
const platform = platformCode ?? this.config.platformCode ?? '';
|
|
565
|
+
const response = await fetch(`${accountsUrl}/api/auth/onboarding/status?platform_code=${platform}&identity_id=${identityId}`, { method: 'GET', headers: { 'Content-Type': 'application/json' }, credentials: 'include' });
|
|
566
|
+
if (!response.ok)
|
|
567
|
+
throw new Error('Failed to check onboarding status');
|
|
568
|
+
return response.json();
|
|
765
569
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
570
|
+
async completeTenantOnboarding(countryCode, tenantName, accessToken) {
|
|
571
|
+
const apiUrl = this.getPlatformApiUrl();
|
|
572
|
+
const response = await fetch(`${apiUrl}/auth/register-tenant`, {
|
|
573
|
+
method: 'POST',
|
|
574
|
+
headers: {
|
|
575
|
+
'Content-Type': 'application/json',
|
|
576
|
+
'Authorization': `Bearer ${accessToken}`
|
|
577
|
+
},
|
|
578
|
+
credentials: 'include',
|
|
579
|
+
body: JSON.stringify({
|
|
580
|
+
platform: this.config.platformCode,
|
|
581
|
+
tenant_name: tenantName,
|
|
582
|
+
country_code: countryCode,
|
|
583
|
+
provider: 'google',
|
|
584
|
+
oauth_token: accessToken
|
|
585
|
+
})
|
|
586
|
+
});
|
|
587
|
+
if (!response.ok) {
|
|
588
|
+
const errorData = await response.json();
|
|
589
|
+
throw new Error(errorData.message || 'Failed to create tenant');
|
|
777
590
|
}
|
|
778
|
-
|
|
591
|
+
return response.json();
|
|
779
592
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
593
|
+
async checkEmail(email) {
|
|
594
|
+
try {
|
|
595
|
+
const accountsUrl = this.getAccountsUrl();
|
|
596
|
+
const response = await fetch(`${accountsUrl}/api/auth/check-email`, {
|
|
597
|
+
method: 'POST',
|
|
598
|
+
headers: { 'Content-Type': 'application/json' },
|
|
599
|
+
body: JSON.stringify({ email })
|
|
600
|
+
});
|
|
601
|
+
const data = await response.json();
|
|
602
|
+
return { exists: data.exists, user: data.user };
|
|
787
603
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
return null;
|
|
604
|
+
catch {
|
|
605
|
+
return { exists: false };
|
|
791
606
|
}
|
|
792
|
-
return this.environment.authServers[targetServer] || null;
|
|
793
607
|
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Configure the ngx-stonescriptphp-client library.
|
|
612
|
+
*
|
|
613
|
+
* @param environment - Library configuration (API server, auth settings, etc.)
|
|
614
|
+
* @param plugin - Optional auth plugin override. Defaults to StoneScriptPHPAuth.
|
|
615
|
+
* Provide your own plugin to use Firebase, progalaxyelabs-auth, Okta, or any other auth backend.
|
|
616
|
+
*
|
|
617
|
+
* @example Default (StoneScriptPHP backend)
|
|
618
|
+
* ```typescript
|
|
619
|
+
* // app.config.ts
|
|
620
|
+
* export const appConfig: ApplicationConfig = {
|
|
621
|
+
* providers: [
|
|
622
|
+
* provideNgxStoneScriptPhpClient(environment)
|
|
623
|
+
* ]
|
|
624
|
+
* };
|
|
625
|
+
* ```
|
|
626
|
+
*
|
|
627
|
+
* @example External auth plugin
|
|
628
|
+
* ```typescript
|
|
629
|
+
* import { ProgalaxyElabsAuth } from './progalaxyelabs-auth.auth-plugin';
|
|
630
|
+
*
|
|
631
|
+
* providers: [
|
|
632
|
+
* provideNgxStoneScriptPhpClient(environment, new ProgalaxyElabsAuth({ host: '...' }))
|
|
633
|
+
* ]
|
|
634
|
+
* ```
|
|
635
|
+
*
|
|
636
|
+
* @example Firebase
|
|
637
|
+
* ```typescript
|
|
638
|
+
* import { FirebaseAuthPlugin } from './firebase-auth.auth-plugin';
|
|
639
|
+
*
|
|
640
|
+
* providers: [
|
|
641
|
+
* provideNgxStoneScriptPhpClient(environment, new FirebaseAuthPlugin(firebaseConfig))
|
|
642
|
+
* ]
|
|
643
|
+
* ```
|
|
644
|
+
*/
|
|
645
|
+
function provideNgxStoneScriptPhpClient(environment, plugin) {
|
|
646
|
+
const resolvedPlugin = plugin ?? new StoneScriptPHPAuth({
|
|
647
|
+
// Resolve auth host: auth.host → accountsServer.host (compat) → accountsUrl (compat) → apiServer.host
|
|
648
|
+
host: environment.auth?.host
|
|
649
|
+
|| environment.accountsServer?.host
|
|
650
|
+
|| environment.accountsUrl
|
|
651
|
+
|| environment.apiServer.host,
|
|
652
|
+
platformCode: environment.platformCode,
|
|
653
|
+
authServers: environment.authServers,
|
|
654
|
+
responseMap: environment.auth?.responseMap ?? environment.authResponseMap,
|
|
655
|
+
auth: environment.auth,
|
|
656
|
+
apiUrl: environment.apiUrl
|
|
657
|
+
});
|
|
658
|
+
return makeEnvironmentProviders([
|
|
659
|
+
{ provide: MyEnvironmentModel, useValue: environment },
|
|
660
|
+
{ provide: AUTH_PLUGIN, useValue: resolvedPlugin }
|
|
661
|
+
]);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
class ApiResponse {
|
|
665
|
+
status;
|
|
666
|
+
data;
|
|
667
|
+
message;
|
|
668
|
+
get success() {
|
|
669
|
+
return this.status === 'ok';
|
|
799
670
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
671
|
+
get errors() {
|
|
672
|
+
return this.message ? [this.message] : [];
|
|
673
|
+
}
|
|
674
|
+
constructor(status, data = null, message = '') {
|
|
675
|
+
this.status = status;
|
|
676
|
+
this.data = data || null;
|
|
677
|
+
this.message = message;
|
|
678
|
+
}
|
|
679
|
+
onOk(callback) {
|
|
680
|
+
if (this.status === 'ok') {
|
|
681
|
+
callback(this.data);
|
|
808
682
|
}
|
|
809
|
-
|
|
810
|
-
|
|
683
|
+
return this;
|
|
684
|
+
}
|
|
685
|
+
onNotOk(callback) {
|
|
686
|
+
if (this.status === 'not ok') {
|
|
687
|
+
callback(this.message, this.data);
|
|
811
688
|
}
|
|
812
|
-
|
|
689
|
+
return this;
|
|
813
690
|
}
|
|
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
|
-
};
|
|
691
|
+
onError(callback) {
|
|
692
|
+
if (this.status === 'error') {
|
|
693
|
+
callback();
|
|
694
|
+
}
|
|
695
|
+
return this;
|
|
825
696
|
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
return path.split('.').reduce((o, key) => o?.[key], obj);
|
|
697
|
+
isSuccess() {
|
|
698
|
+
return this.status === 'ok';
|
|
829
699
|
}
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
700
|
+
isError() {
|
|
701
|
+
return this.status === 'error' || this.status === 'not ok';
|
|
702
|
+
}
|
|
703
|
+
getData() {
|
|
704
|
+
return this.data || null;
|
|
705
|
+
}
|
|
706
|
+
getError() {
|
|
707
|
+
return this.message || 'Unknown error';
|
|
708
|
+
}
|
|
709
|
+
getStatus() {
|
|
710
|
+
return this.status;
|
|
711
|
+
}
|
|
712
|
+
getMessage() {
|
|
713
|
+
return this.message;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
class TokenService {
|
|
718
|
+
accessToken = '';
|
|
719
|
+
refreshToken = '';
|
|
720
|
+
lsAccessTokenKey = 'progalaxyapi_access_token';
|
|
721
|
+
lsRefreshTokenKey = 'progalaxyapi_refresh_token';
|
|
722
|
+
constructor() { }
|
|
723
|
+
setTokens(accessToken, refreshToken) {
|
|
724
|
+
this.accessToken = accessToken;
|
|
725
|
+
this.refreshToken = refreshToken;
|
|
726
|
+
localStorage.setItem(this.lsAccessTokenKey, accessToken);
|
|
727
|
+
localStorage.setItem(this.lsRefreshTokenKey, refreshToken);
|
|
728
|
+
}
|
|
729
|
+
setAccessToken(accessToken) {
|
|
730
|
+
this.accessToken = accessToken;
|
|
731
|
+
localStorage.setItem(this.lsAccessTokenKey, accessToken);
|
|
732
|
+
}
|
|
733
|
+
setRefreshToken(refreshToken) {
|
|
734
|
+
this.refreshToken = refreshToken;
|
|
735
|
+
localStorage.setItem(this.lsRefreshTokenKey, refreshToken);
|
|
736
|
+
}
|
|
737
|
+
getAccessToken() {
|
|
738
|
+
if (this.accessToken) {
|
|
739
|
+
return this.accessToken;
|
|
740
|
+
}
|
|
741
|
+
const storedAccessToken = localStorage.getItem(this.lsAccessTokenKey);
|
|
742
|
+
if (storedAccessToken) {
|
|
743
|
+
return storedAccessToken;
|
|
744
|
+
}
|
|
745
|
+
else {
|
|
746
|
+
return '';
|
|
835
747
|
}
|
|
836
|
-
// No successPath configured — success = access token present
|
|
837
|
-
return !!this.resolvePath(data, map.accessTokenPath);
|
|
838
748
|
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
749
|
+
getRefreshToken() {
|
|
750
|
+
if (this.refreshToken) {
|
|
751
|
+
return this.refreshToken;
|
|
752
|
+
}
|
|
753
|
+
const storedRefreshToken = localStorage.getItem(this.lsRefreshTokenKey);
|
|
754
|
+
if (storedRefreshToken) {
|
|
755
|
+
return storedRefreshToken;
|
|
756
|
+
}
|
|
757
|
+
else {
|
|
758
|
+
return '';
|
|
759
|
+
}
|
|
842
760
|
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
761
|
+
clear() {
|
|
762
|
+
this.accessToken = '';
|
|
763
|
+
this.refreshToken = '';
|
|
764
|
+
localStorage.removeItem(this.lsAccessTokenKey);
|
|
765
|
+
localStorage.removeItem(this.lsRefreshTokenKey);
|
|
846
766
|
}
|
|
847
|
-
/**
|
|
848
|
-
|
|
849
|
-
|
|
767
|
+
/**
|
|
768
|
+
* Check if there is a non-empty access token.
|
|
769
|
+
* Token is treated as opaque — validity is determined by the auth server.
|
|
770
|
+
*/
|
|
771
|
+
hasValidAccessToken() {
|
|
772
|
+
const token = this.getAccessToken();
|
|
773
|
+
return token !== null && token !== '';
|
|
850
774
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
775
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TokenService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
776
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TokenService, providedIn: 'root' });
|
|
777
|
+
}
|
|
778
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TokenService, decorators: [{
|
|
779
|
+
type: Injectable,
|
|
780
|
+
args: [{
|
|
781
|
+
providedIn: 'root'
|
|
782
|
+
}]
|
|
783
|
+
}], ctorParameters: () => [] });
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* @deprecated Use boolean directly. Kept for backward compatibility.
|
|
787
|
+
*/
|
|
788
|
+
var VerifyStatus;
|
|
789
|
+
(function (VerifyStatus) {
|
|
790
|
+
VerifyStatus["initialized"] = "initialized";
|
|
791
|
+
VerifyStatus["yes"] = "yes";
|
|
792
|
+
VerifyStatus["no"] = "no";
|
|
793
|
+
})(VerifyStatus || (VerifyStatus = {}));
|
|
794
|
+
class SigninStatusService {
|
|
795
|
+
status;
|
|
796
|
+
constructor() {
|
|
797
|
+
this.status = new BehaviorSubject(false);
|
|
855
798
|
}
|
|
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
|
-
};
|
|
799
|
+
signedOut() {
|
|
800
|
+
this.status.next(false);
|
|
801
|
+
}
|
|
802
|
+
signedIn() {
|
|
803
|
+
this.status.next(true);
|
|
866
804
|
}
|
|
867
805
|
/**
|
|
868
|
-
*
|
|
869
|
-
*
|
|
806
|
+
* Set signin status
|
|
807
|
+
* @param isSignedIn - True if user is signed in, false otherwise
|
|
870
808
|
*/
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
809
|
+
setSigninStatus(isSignedIn) {
|
|
810
|
+
this.status.next(isSignedIn);
|
|
811
|
+
}
|
|
812
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SigninStatusService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
813
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SigninStatusService, providedIn: 'root' });
|
|
814
|
+
}
|
|
815
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SigninStatusService, decorators: [{
|
|
816
|
+
type: Injectable,
|
|
817
|
+
args: [{
|
|
818
|
+
providedIn: 'root'
|
|
819
|
+
}]
|
|
820
|
+
}], ctorParameters: () => [] });
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* AuthService — manages auth state and delegates all auth operations to the AuthPlugin.
|
|
824
|
+
*
|
|
825
|
+
* This service holds user state (via BehaviorSubject) and tokens (via TokenService).
|
|
826
|
+
* It does not make any HTTP calls directly — all auth logic lives in the plugin.
|
|
827
|
+
*
|
|
828
|
+
* Provide a plugin via provideNgxStoneScriptPhpClient():
|
|
829
|
+
* - Default: StoneScriptPHPAuth (built-in, matches StoneScriptPHP backend)
|
|
830
|
+
* - External: any class implementing AuthPlugin (Firebase, progalaxyelabs-auth, Okta, etc.)
|
|
831
|
+
*/
|
|
832
|
+
class AuthService {
|
|
833
|
+
plugin;
|
|
834
|
+
tokens;
|
|
835
|
+
signinStatus;
|
|
836
|
+
USER_STORAGE_KEY = 'progalaxyapi_user';
|
|
837
|
+
userSubject = new BehaviorSubject(null);
|
|
838
|
+
user$ = this.userSubject.asObservable();
|
|
839
|
+
constructor(plugin, tokens, signinStatus) {
|
|
840
|
+
this.plugin = plugin;
|
|
841
|
+
this.tokens = tokens;
|
|
842
|
+
this.signinStatus = signinStatus;
|
|
843
|
+
this.restoreUser();
|
|
879
844
|
}
|
|
880
|
-
|
|
881
|
-
* Restore user from localStorage
|
|
882
|
-
*/
|
|
845
|
+
// ── State management ──────────────────────────────────────────────────────
|
|
883
846
|
restoreUser() {
|
|
884
847
|
try {
|
|
885
848
|
const userJson = localStorage.getItem(this.USER_STORAGE_KEY);
|
|
886
|
-
if (userJson)
|
|
887
|
-
|
|
888
|
-
this.updateUser(user);
|
|
889
|
-
}
|
|
849
|
+
if (userJson)
|
|
850
|
+
this.updateUser(JSON.parse(userJson));
|
|
890
851
|
}
|
|
891
852
|
catch (error) {
|
|
892
853
|
console.error('Failed to restore user from localStorage:', error);
|
|
893
854
|
}
|
|
894
855
|
}
|
|
895
|
-
/**
|
|
896
|
-
* Save user to localStorage
|
|
897
|
-
*/
|
|
898
856
|
saveUser(user) {
|
|
899
857
|
try {
|
|
900
858
|
if (user) {
|
|
@@ -908,699 +866,435 @@ class AuthService {
|
|
|
908
866
|
console.error('Failed to save user to localStorage:', error);
|
|
909
867
|
}
|
|
910
868
|
}
|
|
911
|
-
/**
|
|
912
|
-
* Update user subject and persist to localStorage
|
|
913
|
-
*/
|
|
914
869
|
updateUser(user) {
|
|
915
870
|
this.userSubject.next(user);
|
|
916
871
|
this.saveUser(user);
|
|
917
872
|
}
|
|
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
|
-
}
|
|
873
|
+
storeAuthResult(result) {
|
|
874
|
+
if (result.accessToken)
|
|
875
|
+
this.tokens.setAccessToken(result.accessToken);
|
|
876
|
+
if (result.refreshToken)
|
|
877
|
+
this.tokens.setRefreshToken(result.refreshToken);
|
|
878
|
+
if (result.user)
|
|
879
|
+
this.updateUser(result.user);
|
|
880
|
+
this.signinStatus.setSigninStatus(true);
|
|
881
|
+
}
|
|
882
|
+
// ── Core auth operations ──────────────────────────────────────────────────
|
|
883
|
+
async loginWithEmail(email, password) {
|
|
884
|
+
const result = await this.plugin.login(email, password);
|
|
885
|
+
if (result.success)
|
|
886
|
+
this.storeAuthResult(result);
|
|
887
|
+
return result;
|
|
961
888
|
}
|
|
962
|
-
/**
|
|
963
|
-
* Login with Google OAuth (popup window)
|
|
964
|
-
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
965
|
-
*/
|
|
966
889
|
async loginWithGoogle(serverName) {
|
|
967
|
-
return this.
|
|
890
|
+
return this.loginWithProvider('google');
|
|
968
891
|
}
|
|
969
|
-
/**
|
|
970
|
-
* Login with GitHub OAuth (popup window)
|
|
971
|
-
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
972
|
-
*/
|
|
973
892
|
async loginWithGitHub(serverName) {
|
|
974
|
-
return this.
|
|
893
|
+
return this.loginWithProvider('github');
|
|
975
894
|
}
|
|
976
|
-
/**
|
|
977
|
-
* Login with LinkedIn OAuth (popup window)
|
|
978
|
-
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
979
|
-
*/
|
|
980
895
|
async loginWithLinkedIn(serverName) {
|
|
981
|
-
return this.
|
|
896
|
+
return this.loginWithProvider('linkedin');
|
|
982
897
|
}
|
|
983
|
-
/**
|
|
984
|
-
* Login with Apple OAuth (popup window)
|
|
985
|
-
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
986
|
-
*/
|
|
987
898
|
async loginWithApple(serverName) {
|
|
988
|
-
return this.
|
|
899
|
+
return this.loginWithProvider('apple');
|
|
989
900
|
}
|
|
990
|
-
/**
|
|
991
|
-
* Login with Microsoft OAuth (popup window)
|
|
992
|
-
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
993
|
-
*/
|
|
994
901
|
async loginWithMicrosoft(serverName) {
|
|
995
|
-
return this.
|
|
902
|
+
return this.loginWithProvider('microsoft');
|
|
996
903
|
}
|
|
997
|
-
/**
|
|
998
|
-
* Login with Zoho OAuth (popup window)
|
|
999
|
-
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
1000
|
-
*/
|
|
1001
904
|
async loginWithZoho(serverName) {
|
|
1002
|
-
return this.
|
|
905
|
+
return this.loginWithProvider('zoho');
|
|
1003
906
|
}
|
|
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
907
|
async loginWithProvider(provider, serverName) {
|
|
1010
908
|
if (provider === 'emailPassword') {
|
|
1011
909
|
throw new Error('Use loginWithEmail() for email/password authentication');
|
|
1012
910
|
}
|
|
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
|
-
});
|
|
911
|
+
if (!this.plugin.loginWithProvider) {
|
|
912
|
+
return { success: false, message: 'OAuth not supported by the configured auth plugin' };
|
|
913
|
+
}
|
|
914
|
+
const result = await this.plugin.loginWithProvider(provider);
|
|
915
|
+
if (result.success)
|
|
916
|
+
this.storeAuthResult(result);
|
|
917
|
+
return result;
|
|
1082
918
|
}
|
|
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
919
|
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
|
-
}
|
|
920
|
+
const result = await this.plugin.register(email, password, displayName);
|
|
921
|
+
if (result.success)
|
|
922
|
+
this.storeAuthResult(result);
|
|
923
|
+
return result;
|
|
1132
924
|
}
|
|
1133
|
-
/**
|
|
1134
|
-
* Sign out user
|
|
1135
|
-
* @param serverName - Optional: Specify which auth server to logout from (for multi-server mode)
|
|
1136
|
-
*/
|
|
1137
925
|
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
|
-
}
|
|
926
|
+
const refreshToken = this.tokens.getRefreshToken() || undefined;
|
|
927
|
+
await this.plugin.logout(refreshToken);
|
|
928
|
+
this.tokens.clear();
|
|
929
|
+
this.signinStatus.setSigninStatus(false);
|
|
930
|
+
this.updateUser(null);
|
|
1162
931
|
}
|
|
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
932
|
async checkSession(serverName) {
|
|
1168
933
|
if (this.tokens.hasValidAccessToken()) {
|
|
1169
934
|
this.signinStatus.setSigninStatus(true);
|
|
1170
935
|
return true;
|
|
1171
936
|
}
|
|
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;
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
/**
|
|
1212
|
-
* Check if user is authenticated
|
|
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
|
|
1227
|
-
*/
|
|
1228
|
-
async registerTenant(data) {
|
|
1229
|
-
try {
|
|
1230
|
-
// If using OAuth, initiate OAuth flow first
|
|
1231
|
-
if (data.provider !== 'emailPassword') {
|
|
1232
|
-
return await this.registerTenantWithOAuth(data.tenantName, data.tenantSlug, data.provider);
|
|
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
|
-
};
|
|
937
|
+
const result = await this.plugin.checkSession();
|
|
938
|
+
if (result.success && result.accessToken) {
|
|
939
|
+
this.tokens.setAccessToken(result.accessToken);
|
|
940
|
+
if (result.user)
|
|
941
|
+
this.updateUser(result.user);
|
|
942
|
+
this.signinStatus.setSigninStatus(true);
|
|
943
|
+
return true;
|
|
1269
944
|
}
|
|
945
|
+
this.tokens.clear();
|
|
946
|
+
this.updateUser(null);
|
|
947
|
+
this.signinStatus.setSigninStatus(false);
|
|
948
|
+
return false;
|
|
1270
949
|
}
|
|
1271
950
|
/**
|
|
1272
|
-
*
|
|
1273
|
-
*
|
|
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)
|
|
951
|
+
* Refresh the access token. Called by ApiConnectionService on 401.
|
|
952
|
+
* @returns true if token was refreshed, false if refresh failed (user is signed out)
|
|
1346
953
|
*/
|
|
1347
|
-
async
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
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: [] };
|
|
954
|
+
async refresh() {
|
|
955
|
+
const newToken = await this.plugin.refresh(this.tokens.getAccessToken(), this.tokens.getRefreshToken() || undefined);
|
|
956
|
+
if (newToken) {
|
|
957
|
+
this.tokens.setAccessToken(newToken);
|
|
958
|
+
return true;
|
|
1365
959
|
}
|
|
960
|
+
this.tokens.clear();
|
|
961
|
+
this.updateUser(null);
|
|
962
|
+
this.signinStatus.signedOut();
|
|
963
|
+
return false;
|
|
1366
964
|
}
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
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
|
-
};
|
|
965
|
+
isAuthenticated() {
|
|
966
|
+
return this.tokens.hasValidAccessToken();
|
|
967
|
+
}
|
|
968
|
+
getCurrentUser() {
|
|
969
|
+
return this.userSubject.value;
|
|
970
|
+
}
|
|
971
|
+
// ── Multi-tenant operations ───────────────────────────────────────────────
|
|
972
|
+
async registerTenant(data) {
|
|
973
|
+
if (!this.plugin.registerTenant) {
|
|
974
|
+
return { success: false, message: 'registerTenant not supported by the configured auth plugin' };
|
|
1398
975
|
}
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
976
|
+
const registered = await this.plugin.registerTenant(data);
|
|
977
|
+
if (!registered.success)
|
|
978
|
+
return registered;
|
|
979
|
+
// OAuth: token comes directly from the popup — store it and we're done.
|
|
980
|
+
if (registered.accessToken) {
|
|
981
|
+
this.storeAuthResult(registered);
|
|
982
|
+
return registered;
|
|
1404
983
|
}
|
|
984
|
+
// emailPassword: registration succeeded but no token yet.
|
|
985
|
+
// Login via auth (SSO) to obtain tokens.
|
|
986
|
+
if (data.provider === 'emailPassword' && data.email && data.password) {
|
|
987
|
+
return await this.loginWithEmail(data.email, data.password);
|
|
988
|
+
}
|
|
989
|
+
return registered;
|
|
1405
990
|
}
|
|
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
|
-
};
|
|
991
|
+
async getTenantMemberships(serverName) {
|
|
992
|
+
if (!this.plugin.getTenantMemberships)
|
|
993
|
+
return { memberships: [] };
|
|
994
|
+
const memberships = await this.plugin.getTenantMemberships(this.tokens.getAccessToken());
|
|
995
|
+
return { memberships };
|
|
996
|
+
}
|
|
997
|
+
async selectTenant(tenantId, serverName) {
|
|
998
|
+
if (!this.plugin.selectTenant) {
|
|
999
|
+
return { success: false, message: 'selectTenant not supported by the configured auth plugin' };
|
|
1423
1000
|
}
|
|
1424
|
-
|
|
1425
|
-
|
|
1001
|
+
const result = await this.plugin.selectTenant(tenantId, this.tokens.getAccessToken());
|
|
1002
|
+
if (result.success && result.accessToken) {
|
|
1003
|
+
this.tokens.setAccessToken(result.accessToken);
|
|
1004
|
+
}
|
|
1005
|
+
return { success: result.success, message: result.message, access_token: result.accessToken };
|
|
1006
|
+
}
|
|
1007
|
+
async checkTenantSlugAvailable(slug, serverName) {
|
|
1008
|
+
if (!this.plugin.checkTenantSlugAvailable)
|
|
1426
1009
|
return { available: true };
|
|
1010
|
+
return this.plugin.checkTenantSlugAvailable(slug);
|
|
1011
|
+
}
|
|
1012
|
+
async checkOnboardingStatus(identityId, serverName) {
|
|
1013
|
+
if (!this.plugin.checkOnboardingStatus)
|
|
1014
|
+
throw new Error('checkOnboardingStatus not supported');
|
|
1015
|
+
return this.plugin.checkOnboardingStatus(identityId);
|
|
1016
|
+
}
|
|
1017
|
+
async completeTenantOnboarding(countryCode, tenantName, serverName) {
|
|
1018
|
+
if (!this.plugin.completeTenantOnboarding)
|
|
1019
|
+
throw new Error('completeTenantOnboarding not supported');
|
|
1020
|
+
const result = await this.plugin.completeTenantOnboarding(countryCode, tenantName, this.tokens.getAccessToken());
|
|
1021
|
+
if (result?.access_token) {
|
|
1022
|
+
this.tokens.setAccessToken(result.access_token);
|
|
1023
|
+
this.signinStatus.setSigninStatus(true);
|
|
1427
1024
|
}
|
|
1025
|
+
return result;
|
|
1428
1026
|
}
|
|
1429
|
-
//
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
/**
|
|
1433
|
-
* @deprecated Use getCurrentUser()?.user_id instead
|
|
1434
|
-
*/
|
|
1435
|
-
getUserId() {
|
|
1436
|
-
return this.userSubject.value?.user_id || 0;
|
|
1027
|
+
// ── Multi-server (delegated to plugin) ────────────────────────────────────
|
|
1028
|
+
getAvailableAuthServers() {
|
|
1029
|
+
return this.plugin.getAvailableServers?.() ?? [];
|
|
1437
1030
|
}
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
*/
|
|
1441
|
-
getUserName() {
|
|
1442
|
-
return this.userSubject.value?.display_name || '';
|
|
1031
|
+
getActiveAuthServer() {
|
|
1032
|
+
return this.plugin.getActiveServer?.() ?? null;
|
|
1443
1033
|
}
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1034
|
+
switchAuthServer(serverName) {
|
|
1035
|
+
if (!this.plugin.switchServer) {
|
|
1036
|
+
throw new Error('Multi-server mode not supported by the configured auth plugin');
|
|
1037
|
+
}
|
|
1038
|
+
this.plugin.switchServer(serverName);
|
|
1449
1039
|
}
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
*/
|
|
1453
|
-
getDisplayName() {
|
|
1454
|
-
return this.userSubject.value?.display_name || '';
|
|
1040
|
+
getAuthServerConfig(serverName) {
|
|
1041
|
+
return this.plugin.getServerConfig?.(serverName) ?? null;
|
|
1455
1042
|
}
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1043
|
+
isMultiServerMode() {
|
|
1044
|
+
return (this.plugin.getAvailableServers?.() ?? []).length > 0;
|
|
1045
|
+
}
|
|
1046
|
+
// ── Backward compatibility ────────────────────────────────────────────────
|
|
1047
|
+
/** @deprecated Use getCurrentUser()?.user_id instead */
|
|
1048
|
+
getUserId() { return this.userSubject.value?.user_id || 0; }
|
|
1049
|
+
/** @deprecated Use getCurrentUser()?.display_name instead */
|
|
1050
|
+
getUserName() { return this.userSubject.value?.display_name || ''; }
|
|
1051
|
+
/** @deprecated Use getCurrentUser()?.photo_url instead */
|
|
1052
|
+
getPhotoUrl() { return this.userSubject.value?.photo_url || ''; }
|
|
1053
|
+
/** @deprecated Use getCurrentUser()?.display_name instead */
|
|
1054
|
+
getDisplayName() { return this.userSubject.value?.display_name || ''; }
|
|
1055
|
+
/** @deprecated Use `/profile/${getCurrentUser()?.user_id}` instead */
|
|
1459
1056
|
getProfileUrl() {
|
|
1460
1057
|
const userId = this.userSubject.value?.user_id;
|
|
1461
1058
|
return userId ? `/profile/${userId}` : '';
|
|
1462
1059
|
}
|
|
1463
|
-
/**
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
async signin() {
|
|
1467
|
-
return this.isAuthenticated();
|
|
1468
|
-
}
|
|
1469
|
-
/**
|
|
1470
|
-
* @deprecated Use loginWithEmail() instead
|
|
1471
|
-
*/
|
|
1060
|
+
/** @deprecated Use isAuthenticated() instead */
|
|
1061
|
+
async signin() { return this.isAuthenticated(); }
|
|
1062
|
+
/** @deprecated Use loginWithEmail() instead */
|
|
1472
1063
|
async verifyCredentials(email, password) {
|
|
1473
1064
|
const result = await this.loginWithEmail(email, password);
|
|
1474
1065
|
return result.success;
|
|
1475
1066
|
}
|
|
1476
|
-
/**
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
}
|
|
1482
|
-
/**
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1067
|
+
/** @deprecated Check user.is_email_verified from getCurrentUser() instead */
|
|
1068
|
+
isSigninEmailValid() { return this.userSubject.value?.is_email_verified || false; }
|
|
1069
|
+
/** @deprecated No longer needed */
|
|
1070
|
+
onDialogClose() { }
|
|
1071
|
+
/** @deprecated No longer needed */
|
|
1072
|
+
closeSocialAuthDialog() { }
|
|
1073
|
+
/** @deprecated Use checkEmail() from the plugin directly */
|
|
1074
|
+
async getUserProfile(email, serverName) {
|
|
1075
|
+
if (!this.plugin.checkEmail)
|
|
1076
|
+
return null;
|
|
1077
|
+
const result = await this.plugin.checkEmail(email);
|
|
1078
|
+
return result.exists ? result.user : null;
|
|
1487
1079
|
}
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1080
|
+
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 });
|
|
1081
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AuthService, providedIn: 'root' });
|
|
1082
|
+
}
|
|
1083
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AuthService, decorators: [{
|
|
1084
|
+
type: Injectable,
|
|
1085
|
+
args: [{
|
|
1086
|
+
providedIn: 'root'
|
|
1087
|
+
}]
|
|
1088
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
1089
|
+
type: Inject,
|
|
1090
|
+
args: [AUTH_PLUGIN]
|
|
1091
|
+
}] }, { type: TokenService }, { type: SigninStatusService }] });
|
|
1092
|
+
|
|
1093
|
+
class ApiConnectionService {
|
|
1094
|
+
tokens;
|
|
1095
|
+
signinStatus;
|
|
1096
|
+
environment;
|
|
1097
|
+
authService;
|
|
1098
|
+
host = ''; // base URL without trailing slash
|
|
1099
|
+
constructor(tokens, signinStatus, environment, authService) {
|
|
1100
|
+
this.tokens = tokens;
|
|
1101
|
+
this.signinStatus = signinStatus;
|
|
1102
|
+
this.environment = environment;
|
|
1103
|
+
this.authService = authService;
|
|
1104
|
+
this.host = environment.apiServer.host;
|
|
1493
1105
|
}
|
|
1494
|
-
|
|
1495
|
-
* @deprecated Check if user exists by calling /api/auth/check-email endpoint
|
|
1496
|
-
*/
|
|
1497
|
-
async getUserProfile(email, serverName) {
|
|
1106
|
+
async request(url, options, data) {
|
|
1498
1107
|
try {
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1108
|
+
if (data !== null) {
|
|
1109
|
+
const body = JSON.stringify(data);
|
|
1110
|
+
options.body = body || {};
|
|
1111
|
+
}
|
|
1112
|
+
const accessTokenIncluded = this.includeAccessToken(options);
|
|
1113
|
+
let response = await fetch(url, options);
|
|
1114
|
+
if (response.status === 401 && accessTokenIncluded) {
|
|
1115
|
+
response = await this.refreshAndRetry(url, options, response);
|
|
1116
|
+
}
|
|
1117
|
+
if (response.ok) {
|
|
1118
|
+
const json = await response.json();
|
|
1119
|
+
return new ApiResponse(json.status, json.data, json.message);
|
|
1120
|
+
}
|
|
1121
|
+
if (response.status === 401) {
|
|
1122
|
+
this.signinStatus.signedOut();
|
|
1123
|
+
}
|
|
1124
|
+
return this.handleError(response);
|
|
1507
1125
|
}
|
|
1508
1126
|
catch (error) {
|
|
1509
|
-
return
|
|
1127
|
+
return this.handleError(error);
|
|
1510
1128
|
}
|
|
1511
1129
|
}
|
|
1130
|
+
handleError(error) {
|
|
1131
|
+
console.error(`Backend returned code ${error.status}, full error: `, error);
|
|
1132
|
+
return new ApiResponse('error');
|
|
1133
|
+
}
|
|
1134
|
+
async get(endpoint, queryParamsObj) {
|
|
1135
|
+
const url = this.host + endpoint + this.buildQueryString(queryParamsObj);
|
|
1136
|
+
const fetchOptions = { mode: 'cors', redirect: 'error' };
|
|
1137
|
+
return this.request(url, fetchOptions, null);
|
|
1138
|
+
}
|
|
1139
|
+
async post(pathWithQueryParams, data) {
|
|
1140
|
+
const url = this.host + pathWithQueryParams;
|
|
1141
|
+
const fetchOptions = {
|
|
1142
|
+
method: 'POST',
|
|
1143
|
+
mode: 'cors',
|
|
1144
|
+
redirect: 'error',
|
|
1145
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1146
|
+
body: JSON.stringify(data)
|
|
1147
|
+
};
|
|
1148
|
+
return this.request(url, fetchOptions, data);
|
|
1149
|
+
}
|
|
1150
|
+
async put(pathWithQueryParams, data) {
|
|
1151
|
+
const url = this.host + pathWithQueryParams;
|
|
1152
|
+
const fetchOptions = {
|
|
1153
|
+
method: 'PUT',
|
|
1154
|
+
mode: 'cors',
|
|
1155
|
+
redirect: 'error',
|
|
1156
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1157
|
+
body: JSON.stringify(data)
|
|
1158
|
+
};
|
|
1159
|
+
return this.request(url, fetchOptions, data);
|
|
1160
|
+
}
|
|
1161
|
+
async patch(pathWithQueryParams, data) {
|
|
1162
|
+
const url = this.host + pathWithQueryParams;
|
|
1163
|
+
const fetchOptions = {
|
|
1164
|
+
method: 'PATCH',
|
|
1165
|
+
mode: 'cors',
|
|
1166
|
+
redirect: 'error',
|
|
1167
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1168
|
+
body: JSON.stringify(data)
|
|
1169
|
+
};
|
|
1170
|
+
return this.request(url, fetchOptions, data);
|
|
1171
|
+
}
|
|
1172
|
+
async delete(endpoint, queryParamsObj) {
|
|
1173
|
+
const url = this.host + endpoint + this.buildQueryString(queryParamsObj);
|
|
1174
|
+
const fetchOptions = { method: 'DELETE', mode: 'cors', redirect: 'error' };
|
|
1175
|
+
return this.request(url, fetchOptions, null);
|
|
1176
|
+
}
|
|
1177
|
+
includeAccessToken(options) {
|
|
1178
|
+
const accessToken = this.tokens.getAccessToken();
|
|
1179
|
+
if (!accessToken)
|
|
1180
|
+
return false;
|
|
1181
|
+
if (!options.headers)
|
|
1182
|
+
options.headers = {};
|
|
1183
|
+
options.headers['Authorization'] = 'Bearer ' + accessToken;
|
|
1184
|
+
return true;
|
|
1185
|
+
}
|
|
1186
|
+
async refreshAndRetry(url, fetchOptions, response) {
|
|
1187
|
+
const refreshed = await this.authService.refresh();
|
|
1188
|
+
if (!refreshed)
|
|
1189
|
+
return response;
|
|
1190
|
+
fetchOptions.headers['Authorization'] = 'Bearer ' + this.tokens.getAccessToken();
|
|
1191
|
+
return fetch(url, fetchOptions);
|
|
1192
|
+
}
|
|
1512
1193
|
/**
|
|
1513
|
-
*
|
|
1514
|
-
*
|
|
1515
|
-
* @param serverName - Optional: Specify which auth server to query (for multi-server mode)
|
|
1194
|
+
* Refresh the access token (delegates to AuthService → AuthPlugin).
|
|
1195
|
+
* Kept public for backward compatibility.
|
|
1516
1196
|
*/
|
|
1517
|
-
async
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
if (
|
|
1526
|
-
|
|
1197
|
+
async refreshAccessToken() {
|
|
1198
|
+
return this.authService.refresh();
|
|
1199
|
+
}
|
|
1200
|
+
buildQueryString(options) {
|
|
1201
|
+
if (options === undefined)
|
|
1202
|
+
return '';
|
|
1203
|
+
const array = [];
|
|
1204
|
+
for (const key in options) {
|
|
1205
|
+
if (options.hasOwnProperty(key) && options[key] !== null && options[key] !== undefined) {
|
|
1206
|
+
array.push(encodeURIComponent(key) + '=' + encodeURIComponent(options[key]));
|
|
1527
1207
|
}
|
|
1528
|
-
return await response.json();
|
|
1529
|
-
}
|
|
1530
|
-
catch (error) {
|
|
1531
|
-
throw error;
|
|
1532
1208
|
}
|
|
1209
|
+
const str = array.join('&');
|
|
1210
|
+
return str ? '?' + str : '';
|
|
1533
1211
|
}
|
|
1534
1212
|
/**
|
|
1535
|
-
*
|
|
1536
|
-
* @
|
|
1537
|
-
* @param tenantName - Tenant organization name
|
|
1538
|
-
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
1213
|
+
* Upload a drawing (uses upload server if configured, otherwise API server)
|
|
1214
|
+
* @deprecated Platform-specific method - consider moving to platform service
|
|
1539
1215
|
*/
|
|
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
|
-
}
|
|
1216
|
+
async uploadDrawing(formData) {
|
|
1217
|
+
const uploadHost = this.environment.uploadServer?.host || this.host;
|
|
1218
|
+
const url = uploadHost + '/upload/drawing';
|
|
1219
|
+
const fetchOptions = { method: 'POST', mode: 'cors', redirect: 'error', body: formData };
|
|
1220
|
+
return this.request(url, fetchOptions, null);
|
|
1221
|
+
}
|
|
1222
|
+
/**
|
|
1223
|
+
* Upload an image (uses upload server if configured, otherwise API server)
|
|
1224
|
+
* @deprecated Platform-specific method - consider moving to platform service
|
|
1225
|
+
*/
|
|
1226
|
+
async uploadImage(formData) {
|
|
1227
|
+
const uploadHost = this.environment.uploadServer?.host || this.host;
|
|
1228
|
+
const url = uploadHost + '/upload/image';
|
|
1229
|
+
const fetchOptions = { method: 'POST', mode: 'cors', redirect: 'error', body: formData };
|
|
1230
|
+
return this.request(url, fetchOptions, null);
|
|
1578
1231
|
}
|
|
1579
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1580
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
1232
|
+
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 });
|
|
1233
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ApiConnectionService, providedIn: 'root' });
|
|
1581
1234
|
}
|
|
1582
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1235
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ApiConnectionService, decorators: [{
|
|
1583
1236
|
type: Injectable,
|
|
1584
1237
|
args: [{
|
|
1585
1238
|
providedIn: 'root'
|
|
1586
1239
|
}]
|
|
1587
|
-
}], ctorParameters: () => [{ type: TokenService }, { type: SigninStatusService }, { type: MyEnvironmentModel,
|
|
1588
|
-
type: Inject,
|
|
1589
|
-
args: [MyEnvironmentModel]
|
|
1590
|
-
}] }] });
|
|
1240
|
+
}], ctorParameters: () => [{ type: TokenService }, { type: SigninStatusService }, { type: MyEnvironmentModel }, { type: AuthService }] });
|
|
1591
1241
|
|
|
1592
1242
|
class DbService {
|
|
1593
1243
|
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.
|
|
1244
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DbService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1245
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DbService, providedIn: 'root' });
|
|
1596
1246
|
}
|
|
1597
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1247
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DbService, decorators: [{
|
|
1598
1248
|
type: Injectable,
|
|
1599
1249
|
args: [{
|
|
1600
1250
|
providedIn: 'root'
|
|
1601
1251
|
}]
|
|
1602
1252
|
}], ctorParameters: () => [] });
|
|
1603
1253
|
|
|
1254
|
+
/**
|
|
1255
|
+
* CSRF Token Service
|
|
1256
|
+
*
|
|
1257
|
+
* Manages CSRF tokens for cookie-based authentication.
|
|
1258
|
+
* Reads CSRF token from cookies and provides it for request headers.
|
|
1259
|
+
*/
|
|
1260
|
+
class CsrfService {
|
|
1261
|
+
/**
|
|
1262
|
+
* Get CSRF token from cookie
|
|
1263
|
+
*/
|
|
1264
|
+
getCsrfToken(cookieName = 'csrf_token') {
|
|
1265
|
+
const cookies = document.cookie.split(';');
|
|
1266
|
+
for (const cookie of cookies) {
|
|
1267
|
+
const [name, value] = cookie.trim().split('=');
|
|
1268
|
+
if (name === cookieName) {
|
|
1269
|
+
return decodeURIComponent(value);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
return null;
|
|
1273
|
+
}
|
|
1274
|
+
/**
|
|
1275
|
+
* Check if CSRF token exists
|
|
1276
|
+
*/
|
|
1277
|
+
hasCsrfToken(cookieName = 'csrf_token') {
|
|
1278
|
+
return this.getCsrfToken(cookieName) !== null;
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Clear CSRF token (for logout)
|
|
1282
|
+
* Note: Client-side deletion is limited for httpOnly cookies
|
|
1283
|
+
*/
|
|
1284
|
+
clearCsrfToken(cookieName = 'csrf_token') {
|
|
1285
|
+
// Can only clear non-httpOnly cookies
|
|
1286
|
+
document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
|
|
1287
|
+
}
|
|
1288
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CsrfService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1289
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CsrfService, providedIn: 'root' });
|
|
1290
|
+
}
|
|
1291
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CsrfService, decorators: [{
|
|
1292
|
+
type: Injectable,
|
|
1293
|
+
args: [{
|
|
1294
|
+
providedIn: 'root'
|
|
1295
|
+
}]
|
|
1296
|
+
}] });
|
|
1297
|
+
|
|
1604
1298
|
/**
|
|
1605
1299
|
* Service for interacting with the stonescriptphp-files server.
|
|
1606
1300
|
* Handles file upload, download, list, and delete operations
|
|
@@ -1855,10 +1549,10 @@ class FilesService {
|
|
|
1855
1549
|
return false;
|
|
1856
1550
|
}
|
|
1857
1551
|
}
|
|
1858
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1859
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
1552
|
+
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 });
|
|
1553
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: FilesService, providedIn: 'root' });
|
|
1860
1554
|
}
|
|
1861
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1555
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: FilesService, decorators: [{
|
|
1862
1556
|
type: Injectable,
|
|
1863
1557
|
args: [{
|
|
1864
1558
|
providedIn: 'root'
|
|
@@ -2034,10 +1728,10 @@ class ProviderRegistryService {
|
|
|
2034
1728
|
}
|
|
2035
1729
|
return Object.keys(styles).length > 0 ? styles : null;
|
|
2036
1730
|
}
|
|
2037
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
2038
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
1731
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ProviderRegistryService, deps: [{ token: MyEnvironmentModel }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1732
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ProviderRegistryService, providedIn: 'root' });
|
|
2039
1733
|
}
|
|
2040
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1734
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ProviderRegistryService, decorators: [{
|
|
2041
1735
|
type: Injectable,
|
|
2042
1736
|
args: [{
|
|
2043
1737
|
providedIn: 'root'
|
|
@@ -2047,29 +1741,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
2047
1741
|
args: [MyEnvironmentModel]
|
|
2048
1742
|
}] }] });
|
|
2049
1743
|
|
|
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
1744
|
class TenantLoginComponent {
|
|
2074
1745
|
auth;
|
|
2075
1746
|
providerRegistry;
|
|
@@ -2297,8 +1968,8 @@ class TenantLoginComponent {
|
|
|
2297
1968
|
event.preventDefault();
|
|
2298
1969
|
this.createTenant.emit();
|
|
2299
1970
|
}
|
|
2300
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
2301
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.
|
|
1971
|
+
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 });
|
|
1972
|
+
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
1973
|
<div class="tenant-login-dialog">
|
|
2303
1974
|
@if (!showingTenantSelector) {
|
|
2304
1975
|
<!-- Step 1: Authentication -->
|
|
@@ -2466,7 +2137,7 @@ class TenantLoginComponent {
|
|
|
2466
2137
|
</div>
|
|
2467
2138
|
`, 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
2139
|
}
|
|
2469
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
2140
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TenantLoginComponent, decorators: [{
|
|
2470
2141
|
type: Component,
|
|
2471
2142
|
args: [{ selector: 'lib-tenant-login', standalone: true, imports: [CommonModule, FormsModule], template: `
|
|
2472
2143
|
<div class="tenant-login-dialog">
|
|
@@ -2771,8 +2442,8 @@ class RegisterComponent {
|
|
|
2771
2442
|
this.confirmPassword = '';
|
|
2772
2443
|
this.displayName = '';
|
|
2773
2444
|
}
|
|
2774
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
2775
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.
|
|
2445
|
+
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 });
|
|
2446
|
+
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
2447
|
<div class="register-dialog">
|
|
2777
2448
|
<h2 class="register-title">Create Account</h2>
|
|
2778
2449
|
|
|
@@ -2901,7 +2572,7 @@ class RegisterComponent {
|
|
|
2901
2572
|
</div>
|
|
2902
2573
|
`, 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
2574
|
}
|
|
2904
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
2575
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: RegisterComponent, decorators: [{
|
|
2905
2576
|
type: Component,
|
|
2906
2577
|
args: [{ selector: 'lib-register', standalone: true, imports: [CommonModule, FormsModule], template: `
|
|
2907
2578
|
<div class="register-dialog">
|
|
@@ -3087,8 +2758,8 @@ class AuthPageComponent {
|
|
|
3087
2758
|
(B < 255 ? B < 1 ? 0 : B : 255))
|
|
3088
2759
|
.toString(16).slice(1);
|
|
3089
2760
|
}
|
|
3090
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3091
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.
|
|
2761
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AuthPageComponent, deps: [{ token: MyEnvironmentModel }], target: i0.ɵɵFactoryTarget.Component });
|
|
2762
|
+
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
2763
|
<div class="auth-container" [style.background]="gradientStyle">
|
|
3093
2764
|
<div class="auth-card">
|
|
3094
2765
|
@if (logo) {
|
|
@@ -3115,7 +2786,7 @@ class AuthPageComponent {
|
|
|
3115
2786
|
</div>
|
|
3116
2787
|
`, 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
2788
|
}
|
|
3118
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
2789
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AuthPageComponent, decorators: [{
|
|
3119
2790
|
type: Component,
|
|
3120
2791
|
args: [{ selector: 'lib-auth-page', standalone: true, imports: [CommonModule, TenantLoginComponent, RegisterComponent], template: `
|
|
3121
2792
|
<div class="auth-container" [style.background]="gradientStyle">
|
|
@@ -3238,8 +2909,8 @@ class LoginDialogComponent {
|
|
|
3238
2909
|
// For now, just emit a console message
|
|
3239
2910
|
console.log('Register clicked - platform should handle navigation');
|
|
3240
2911
|
}
|
|
3241
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3242
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.
|
|
2912
|
+
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 });
|
|
2913
|
+
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
2914
|
<div class="login-dialog">
|
|
3244
2915
|
<h2 class="login-title">Sign In</h2>
|
|
3245
2916
|
|
|
@@ -3329,7 +3000,7 @@ class LoginDialogComponent {
|
|
|
3329
3000
|
</div>
|
|
3330
3001
|
`, 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
3002
|
}
|
|
3332
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
3003
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: LoginDialogComponent, decorators: [{
|
|
3333
3004
|
type: Component,
|
|
3334
3005
|
args: [{ selector: 'lib-login-dialog', standalone: true, imports: [CommonModule, FormsModule], template: `
|
|
3335
3006
|
<div class="login-dialog">
|
|
@@ -3584,15 +3255,11 @@ class TenantRegisterComponent {
|
|
|
3584
3255
|
try {
|
|
3585
3256
|
const result = await this.auth.registerTenant({
|
|
3586
3257
|
tenantName: this.tenantName,
|
|
3587
|
-
tenantSlug: this.tenantSlug,
|
|
3588
3258
|
provider: provider
|
|
3589
3259
|
});
|
|
3590
|
-
if (result.success && result.
|
|
3260
|
+
if (result.success && result.user) {
|
|
3591
3261
|
this.success = 'Organization created successfully!';
|
|
3592
|
-
this.tenantCreated.emit({
|
|
3593
|
-
tenant: result.tenant,
|
|
3594
|
-
user: result.user
|
|
3595
|
-
});
|
|
3262
|
+
this.tenantCreated.emit({ user: result.user });
|
|
3596
3263
|
}
|
|
3597
3264
|
else {
|
|
3598
3265
|
this.error = result.message || 'Registration failed';
|
|
@@ -3617,18 +3284,14 @@ class TenantRegisterComponent {
|
|
|
3617
3284
|
try {
|
|
3618
3285
|
const result = await this.auth.registerTenant({
|
|
3619
3286
|
tenantName: this.tenantName,
|
|
3620
|
-
tenantSlug: this.tenantSlug,
|
|
3621
3287
|
displayName: this.displayName,
|
|
3622
3288
|
email: this.email,
|
|
3623
3289
|
password: this.password,
|
|
3624
3290
|
provider: 'emailPassword'
|
|
3625
3291
|
});
|
|
3626
|
-
if (result.success && result.
|
|
3292
|
+
if (result.success && result.user) {
|
|
3627
3293
|
this.success = 'Organization created successfully!';
|
|
3628
|
-
this.tenantCreated.emit({
|
|
3629
|
-
tenant: result.tenant,
|
|
3630
|
-
user: result.user
|
|
3631
|
-
});
|
|
3294
|
+
this.tenantCreated.emit({ user: result.user });
|
|
3632
3295
|
}
|
|
3633
3296
|
else {
|
|
3634
3297
|
this.error = result.message || 'Registration failed';
|
|
@@ -3645,8 +3308,8 @@ class TenantRegisterComponent {
|
|
|
3645
3308
|
event.preventDefault();
|
|
3646
3309
|
this.navigateToLogin.emit();
|
|
3647
3310
|
}
|
|
3648
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3649
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.
|
|
3311
|
+
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 });
|
|
3312
|
+
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
3313
|
<div class="tenant-register-dialog">
|
|
3651
3314
|
<h2 class="register-title">{{ title }}</h2>
|
|
3652
3315
|
|
|
@@ -3856,7 +3519,7 @@ class TenantRegisterComponent {
|
|
|
3856
3519
|
</div>
|
|
3857
3520
|
`, 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
3521
|
}
|
|
3859
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
3522
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TenantRegisterComponent, decorators: [{
|
|
3860
3523
|
type: Component,
|
|
3861
3524
|
args: [{ selector: 'lib-tenant-register', standalone: true, imports: [CommonModule, FormsModule], template: `
|
|
3862
3525
|
<div class="tenant-register-dialog">
|
|
@@ -4156,8 +3819,8 @@ class TenantLoginDialogComponent {
|
|
|
4156
3819
|
this.dialogRef.close({ action: 'create_tenant' });
|
|
4157
3820
|
}
|
|
4158
3821
|
}
|
|
4159
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
4160
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.
|
|
3822
|
+
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 });
|
|
3823
|
+
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
3824
|
<div class="dialog-wrapper">
|
|
4162
3825
|
<lib-tenant-login
|
|
4163
3826
|
[title]="data?.title || 'Sign In'"
|
|
@@ -4178,7 +3841,7 @@ class TenantLoginDialogComponent {
|
|
|
4178
3841
|
</div>
|
|
4179
3842
|
`, 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
3843
|
}
|
|
4181
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
3844
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TenantLoginDialogComponent, decorators: [{
|
|
4182
3845
|
type: Component,
|
|
4183
3846
|
args: [{ selector: 'lib-tenant-login-dialog', standalone: true, imports: [CommonModule, TenantLoginComponent], template: `
|
|
4184
3847
|
<div class="dialog-wrapper">
|
|
@@ -4259,8 +3922,8 @@ class TenantRegisterDialogComponent {
|
|
|
4259
3922
|
this.dialogRef.close({ action: 'navigate_to_login' });
|
|
4260
3923
|
}
|
|
4261
3924
|
}
|
|
4262
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
4263
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.
|
|
3925
|
+
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 });
|
|
3926
|
+
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
3927
|
<div class="dialog-wrapper">
|
|
4265
3928
|
<lib-tenant-register
|
|
4266
3929
|
[title]="data?.title || 'Create New Organization'"
|
|
@@ -4286,7 +3949,7 @@ class TenantRegisterDialogComponent {
|
|
|
4286
3949
|
</div>
|
|
4287
3950
|
`, 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
3951
|
}
|
|
4289
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
3952
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TenantRegisterDialogComponent, decorators: [{
|
|
4290
3953
|
type: Component,
|
|
4291
3954
|
args: [{ selector: 'lib-tenant-register-dialog', standalone: true, imports: [CommonModule, TenantRegisterComponent], template: `
|
|
4292
3955
|
<div class="dialog-wrapper">
|
|
@@ -4328,10 +3991,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
4328
3991
|
/*
|
|
4329
3992
|
* Public API Surface of ngx-stonescriptphp-client
|
|
4330
3993
|
*/
|
|
3994
|
+
// ── Core setup ────────────────────────────────────────────────────────────────
|
|
4331
3995
|
|
|
4332
3996
|
/**
|
|
4333
3997
|
* Generated bundle index. Do not edit.
|
|
4334
3998
|
*/
|
|
4335
3999
|
|
|
4336
|
-
export { ApiConnectionService, ApiResponse, AuthPageComponent, AuthService, CsrfService, DbService, FilesService, LoginDialogComponent, MyEnvironmentModel,
|
|
4000
|
+
export { AUTH_PLUGIN, ApiConnectionService, ApiResponse, AuthPageComponent, AuthService, CsrfService, DbService, FilesService, LoginDialogComponent, MyEnvironmentModel, ProviderRegistryService, RegisterComponent, SigninStatusService, StoneScriptPHPAuth, TenantLoginComponent, TenantLoginDialogComponent, TenantRegisterComponent, TenantRegisterDialogComponent, TokenService, VerifyStatus, provideNgxStoneScriptPhpClient };
|
|
4337
4001
|
//# sourceMappingURL=progalaxyelabs-ngx-stonescriptphp-client.mjs.map
|