@symbo.ls/sdk 2.34.34 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -315
- package/dist/cjs/config/environment.js +50 -124
- package/dist/cjs/index.js +22 -169
- package/dist/cjs/services/AIService.js +155 -0
- package/dist/cjs/services/AuthService.js +277 -751
- package/dist/cjs/services/BaseService.js +6 -158
- package/dist/cjs/services/BasedService.js +679 -0
- package/dist/cjs/services/SocketIOService.js +309 -0
- package/dist/cjs/services/SocketService.js +161 -0
- package/dist/cjs/services/SymstoryService.js +485 -0
- package/dist/cjs/services/index.js +16 -64
- package/dist/cjs/utils/basedQuerys.js +61 -0
- package/dist/cjs/utils/permission.js +4 -4
- package/dist/cjs/utils/services.js +80 -301
- package/dist/cjs/utils/symstoryClient.js +228 -0
- package/dist/cjs/utils/validation.js +3 -0
- package/dist/esm/config/environment.js +50 -124
- package/dist/esm/index.js +11282 -49789
- package/dist/esm/services/AIService.js +185 -0
- package/dist/esm/services/AuthService.js +358 -1506
- package/dist/esm/services/BaseService.js +6 -757
- package/dist/esm/services/BasedService.js +4651 -0
- package/dist/esm/services/SocketIOService.js +467 -0
- package/dist/esm/services/SocketService.js +191 -0
- package/dist/esm/services/SymstoryService.js +6849 -0
- package/dist/esm/services/index.js +11188 -49352
- package/dist/esm/utils/basedQuerys.js +43 -0
- package/dist/esm/utils/permission.js +4 -4
- package/dist/esm/utils/services.js +80 -301
- package/dist/esm/utils/symstoryClient.js +334 -0
- package/dist/esm/utils/validation.js +26 -93
- package/dist/node/config/environment.js +50 -124
- package/dist/node/index.js +26 -202
- package/dist/node/services/AIService.js +136 -0
- package/dist/node/services/AuthService.js +278 -751
- package/dist/node/services/BaseService.js +6 -148
- package/dist/node/services/BasedService.js +650 -0
- package/dist/node/services/SocketIOService.js +280 -0
- package/dist/node/services/SocketService.js +142 -0
- package/dist/node/services/SymstoryService.js +456 -0
- package/dist/node/services/index.js +16 -64
- package/dist/node/utils/basedQuerys.js +42 -0
- package/dist/node/utils/permission.js +4 -4
- package/dist/node/utils/services.js +80 -301
- package/dist/node/utils/symstoryClient.js +199 -0
- package/dist/node/utils/validation.js +3 -0
- package/package.json +21 -43
- package/src/config/environment.js +50 -126
- package/src/index.js +24 -208
- package/src/services/AIService.js +150 -0
- package/src/services/AuthService.js +298 -893
- package/src/services/BaseService.js +6 -166
- package/src/services/BasedService.js +722 -0
- package/src/services/SocketIOService.js +356 -0
- package/src/services/SocketService.js +168 -0
- package/src/services/SymstoryService.js +563 -0
- package/src/services/index.js +13 -80
- package/src/utils/basedQuerys.js +41 -0
- package/src/utils/permission.js +4 -4
- package/src/utils/services.js +83 -325
- package/src/utils/symstoryClient.js +218 -0
- package/src/utils/validation.js +3 -0
- package/dist/cjs/services/AdminService.js +0 -351
- package/dist/cjs/services/BranchService.js +0 -484
- package/dist/cjs/services/CollabService.js +0 -743
- package/dist/cjs/services/DnsService.js +0 -340
- package/dist/cjs/services/FeatureFlagService.js +0 -175
- package/dist/cjs/services/FileService.js +0 -201
- package/dist/cjs/services/IntegrationService.js +0 -538
- package/dist/cjs/services/MetricsService.js +0 -62
- package/dist/cjs/services/PaymentService.js +0 -271
- package/dist/cjs/services/PlanService.js +0 -426
- package/dist/cjs/services/ProjectService.js +0 -1207
- package/dist/cjs/services/PullRequestService.js +0 -503
- package/dist/cjs/services/ScreenshotService.js +0 -304
- package/dist/cjs/services/SubscriptionService.js +0 -396
- package/dist/cjs/services/TrackingService.js +0 -661
- package/dist/cjs/services/WaitlistService.js +0 -148
- package/dist/cjs/state/RootStateManager.js +0 -65
- package/dist/cjs/state/rootEventBus.js +0 -74
- package/dist/cjs/utils/CollabClient.js +0 -223
- package/dist/cjs/utils/TokenManager.js +0 -422
- package/dist/cjs/utils/changePreprocessor.js +0 -199
- package/dist/cjs/utils/jsonDiff.js +0 -145
- package/dist/cjs/utils/ordering.js +0 -309
- package/dist/esm/services/AdminService.js +0 -1132
- package/dist/esm/services/BranchService.js +0 -1265
- package/dist/esm/services/CollabService.js +0 -26838
- package/dist/esm/services/DnsService.js +0 -1121
- package/dist/esm/services/FeatureFlagService.js +0 -956
- package/dist/esm/services/FileService.js +0 -982
- package/dist/esm/services/IntegrationService.js +0 -1319
- package/dist/esm/services/MetricsService.js +0 -843
- package/dist/esm/services/PaymentService.js +0 -1052
- package/dist/esm/services/PlanService.js +0 -1207
- package/dist/esm/services/ProjectService.js +0 -2526
- package/dist/esm/services/PullRequestService.js +0 -1284
- package/dist/esm/services/ScreenshotService.js +0 -1085
- package/dist/esm/services/SubscriptionService.js +0 -1177
- package/dist/esm/services/TrackingService.js +0 -18343
- package/dist/esm/services/WaitlistService.js +0 -929
- package/dist/esm/state/RootStateManager.js +0 -90
- package/dist/esm/state/rootEventBus.js +0 -56
- package/dist/esm/utils/CollabClient.js +0 -18901
- package/dist/esm/utils/TokenManager.js +0 -408
- package/dist/esm/utils/changePreprocessor.js +0 -542
- package/dist/esm/utils/jsonDiff.js +0 -7011
- package/dist/esm/utils/ordering.js +0 -291
- package/dist/node/services/AdminService.js +0 -332
- package/dist/node/services/BranchService.js +0 -465
- package/dist/node/services/CollabService.js +0 -724
- package/dist/node/services/DnsService.js +0 -321
- package/dist/node/services/FeatureFlagService.js +0 -156
- package/dist/node/services/FileService.js +0 -182
- package/dist/node/services/IntegrationService.js +0 -519
- package/dist/node/services/MetricsService.js +0 -43
- package/dist/node/services/PaymentService.js +0 -252
- package/dist/node/services/PlanService.js +0 -407
- package/dist/node/services/ProjectService.js +0 -1188
- package/dist/node/services/PullRequestService.js +0 -484
- package/dist/node/services/ScreenshotService.js +0 -285
- package/dist/node/services/SubscriptionService.js +0 -377
- package/dist/node/services/TrackingService.js +0 -632
- package/dist/node/services/WaitlistService.js +0 -129
- package/dist/node/state/RootStateManager.js +0 -36
- package/dist/node/state/rootEventBus.js +0 -55
- package/dist/node/utils/CollabClient.js +0 -194
- package/dist/node/utils/TokenManager.js +0 -403
- package/dist/node/utils/changePreprocessor.js +0 -180
- package/dist/node/utils/jsonDiff.js +0 -116
- package/dist/node/utils/ordering.js +0 -290
- package/src/services/AdminService.js +0 -374
- package/src/services/BranchService.js +0 -536
- package/src/services/CollabService.js +0 -900
- package/src/services/DnsService.js +0 -366
- package/src/services/FeatureFlagService.js +0 -174
- package/src/services/FileService.js +0 -213
- package/src/services/IntegrationService.js +0 -548
- package/src/services/MetricsService.js +0 -40
- package/src/services/PaymentService.js +0 -287
- package/src/services/PlanService.js +0 -468
- package/src/services/ProjectService.js +0 -1366
- package/src/services/PullRequestService.js +0 -537
- package/src/services/ScreenshotService.js +0 -258
- package/src/services/SubscriptionService.js +0 -425
- package/src/services/TrackingService.js +0 -853
- package/src/services/WaitlistService.js +0 -130
- package/src/services/tests/BranchService/createBranch.test.js +0 -153
- package/src/services/tests/BranchService/deleteBranch.test.js +0 -173
- package/src/services/tests/BranchService/getBranchChanges.test.js +0 -146
- package/src/services/tests/BranchService/listBranches.test.js +0 -87
- package/src/services/tests/BranchService/mergeBranch.test.js +0 -210
- package/src/services/tests/BranchService/publishVersion.test.js +0 -183
- package/src/services/tests/BranchService/renameBranch.test.js +0 -240
- package/src/services/tests/BranchService/resetBranch.test.js +0 -152
- package/src/services/tests/FeatureFlagService/adminFeatureFlags.test.js +0 -67
- package/src/services/tests/FeatureFlagService/getFeatureFlags.test.js +0 -75
- package/src/services/tests/FileService/createFileFormData.test.js +0 -74
- package/src/services/tests/FileService/getFileUrl.test.js +0 -69
- package/src/services/tests/FileService/updateProjectIcon.test.js +0 -109
- package/src/services/tests/FileService/uploadDocument.test.js +0 -36
- package/src/services/tests/FileService/uploadFile.test.js +0 -78
- package/src/services/tests/FileService/uploadFileWithValidation.test.js +0 -114
- package/src/services/tests/FileService/uploadImage.test.js +0 -36
- package/src/services/tests/FileService/uploadMultipleFiles.test.js +0 -111
- package/src/services/tests/FileService/validateFile.test.js +0 -63
- package/src/services/tests/PlanService/createPlan.test.js +0 -104
- package/src/services/tests/PlanService/createPlanWithValidation.test.js +0 -523
- package/src/services/tests/PlanService/deletePlan.test.js +0 -92
- package/src/services/tests/PlanService/getActivePlans.test.js +0 -123
- package/src/services/tests/PlanService/getAdminPlans.test.js +0 -84
- package/src/services/tests/PlanService/getPlan.test.js +0 -50
- package/src/services/tests/PlanService/getPlanByKey.test.js +0 -109
- package/src/services/tests/PlanService/getPlanWithValidation.test.js +0 -85
- package/src/services/tests/PlanService/getPlans.test.js +0 -53
- package/src/services/tests/PlanService/getPlansByPriceRange.test.js +0 -109
- package/src/services/tests/PlanService/getPlansWithValidation.test.js +0 -48
- package/src/services/tests/PlanService/initializePlans.test.js +0 -75
- package/src/services/tests/PlanService/updatePlan.test.js +0 -111
- package/src/services/tests/PlanService/updatePlanWithValidation.test.js +0 -556
- package/src/state/RootStateManager.js +0 -76
- package/src/state/rootEventBus.js +0 -67
- package/src/utils/CollabClient.js +0 -248
- package/src/utils/TokenManager.js +0 -479
- package/src/utils/changePreprocessor.js +0 -239
- package/src/utils/jsonDiff.js +0 -144
- package/src/utils/ordering.js +0 -271
|
@@ -1,885 +1,173 @@
|
|
|
1
|
+
/* eslint-disable require-await */
|
|
1
2
|
import { BaseService } from './BaseService.js'
|
|
2
3
|
import {
|
|
4
|
+
PERMISSION_MAP,
|
|
3
5
|
ROLE_PERMISSIONS,
|
|
4
6
|
TIER_FEATURES,
|
|
5
7
|
PROJECT_ROLE_PERMISSIONS
|
|
6
8
|
} from '../utils/permission.js'
|
|
7
9
|
|
|
8
|
-
const PLUGIN_SESSION_STORAGE_KEY = 'plugin_auth_session'
|
|
9
|
-
|
|
10
10
|
export class AuthService extends BaseService {
|
|
11
|
-
constructor(config) {
|
|
11
|
+
constructor (config) {
|
|
12
12
|
super(config)
|
|
13
13
|
this._userRoles = new Set(['guest', 'editor', 'admin', 'owner'])
|
|
14
14
|
this._projectTiers = new Set([
|
|
15
15
|
'ready',
|
|
16
|
-
'
|
|
16
|
+
'free',
|
|
17
17
|
'pro1',
|
|
18
18
|
'pro2',
|
|
19
19
|
'enterprise'
|
|
20
20
|
])
|
|
21
|
-
this.
|
|
22
|
-
this._roleCacheExpiry = 5 * 60 * 1000 // 5 minutes cache expiry
|
|
23
|
-
this._pluginSession = null
|
|
24
|
-
|
|
25
|
-
this._resolvePluginSession(
|
|
26
|
-
config?.session ||
|
|
27
|
-
config?.pluginSession ||
|
|
28
|
-
config?.options?.pluginSession ||
|
|
29
|
-
config?.context?.pluginSession ||
|
|
30
|
-
null
|
|
31
|
-
)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Use BaseService.init/_request/_requireReady implementations
|
|
35
|
-
|
|
36
|
-
// ==================== AUTH METHODS ====================
|
|
37
|
-
|
|
38
|
-
async register(userData, options = {}) {
|
|
39
|
-
try {
|
|
40
|
-
const { payload, session } = this._preparePluginPayload(
|
|
41
|
-
{ ...(userData || {}) },
|
|
42
|
-
options.session
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
const response = await this._request('/auth/register', {
|
|
46
|
-
method: 'POST',
|
|
47
|
-
body: JSON.stringify(payload),
|
|
48
|
-
methodName: 'register'
|
|
49
|
-
})
|
|
50
|
-
if (response.success) {
|
|
51
|
-
if (session) {
|
|
52
|
-
this._clearPluginSession(session)
|
|
53
|
-
}
|
|
54
|
-
return response.data
|
|
55
|
-
}
|
|
56
|
-
throw new Error(response.message)
|
|
57
|
-
} catch (error) {
|
|
58
|
-
throw new Error(`Registration failed: ${error.message}`, { cause: error })
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async login(email, password, options = {}) {
|
|
63
|
-
try {
|
|
64
|
-
const { payload, session } = this._preparePluginPayload(
|
|
65
|
-
{
|
|
66
|
-
email,
|
|
67
|
-
password
|
|
68
|
-
},
|
|
69
|
-
options.session
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
const response = await this._request('/auth/login', {
|
|
73
|
-
method: 'POST',
|
|
74
|
-
body: JSON.stringify(payload),
|
|
75
|
-
methodName: 'login'
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
// Handle new response format: response.data.tokens
|
|
79
|
-
if (response.success && response.data && response.data.tokens) {
|
|
80
|
-
const { tokens } = response.data
|
|
81
|
-
const tokenData = {
|
|
82
|
-
access_token: tokens.accessToken,
|
|
83
|
-
refresh_token: tokens.refreshToken,
|
|
84
|
-
expires_in: tokens.accessTokenExp?.expiresIn,
|
|
85
|
-
token_type: 'Bearer'
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Set tokens in TokenManager (will handle persistence and refresh scheduling)
|
|
89
|
-
if (this._tokenManager) {
|
|
90
|
-
this._tokenManager.setTokens(tokenData)
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (response.success) {
|
|
95
|
-
if (session) {
|
|
96
|
-
this._clearPluginSession(session)
|
|
97
|
-
}
|
|
98
|
-
return response.data
|
|
99
|
-
}
|
|
100
|
-
throw new Error(response.message)
|
|
101
|
-
} catch (error) {
|
|
102
|
-
throw new Error(`Login failed: ${error.message}`, { cause: error })
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async logout() {
|
|
107
|
-
this._requireReady('logout')
|
|
108
|
-
try {
|
|
109
|
-
// Call the logout API endpoint
|
|
110
|
-
await this._request('/auth/logout', {
|
|
111
|
-
method: 'POST',
|
|
112
|
-
methodName: 'logout'
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
// Clear tokens from TokenManager and context
|
|
116
|
-
if (this._tokenManager) {
|
|
117
|
-
this._tokenManager.clearTokens()
|
|
118
|
-
}
|
|
119
|
-
} catch (error) {
|
|
120
|
-
// Even if the API call fails, clear local tokens
|
|
121
|
-
if (this._tokenManager) {
|
|
122
|
-
this._tokenManager.clearTokens()
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
throw new Error(`Logout failed: ${error.message}`, { cause: error })
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
async refreshToken(refreshToken) {
|
|
130
|
-
try {
|
|
131
|
-
const response = await this._request('/auth/refresh', {
|
|
132
|
-
method: 'POST',
|
|
133
|
-
body: JSON.stringify({ refreshToken }),
|
|
134
|
-
methodName: 'refreshToken'
|
|
135
|
-
})
|
|
136
|
-
if (response.success) {
|
|
137
|
-
return response.data
|
|
138
|
-
}
|
|
139
|
-
throw new Error(response.message)
|
|
140
|
-
} catch (error) {
|
|
141
|
-
throw new Error(`Token refresh failed: ${error.message}`, { cause: error })
|
|
142
|
-
}
|
|
21
|
+
this._initialized = false
|
|
143
22
|
}
|
|
144
23
|
|
|
145
|
-
|
|
24
|
+
// eslint-disable-next-line no-empty-pattern
|
|
25
|
+
init ({}) {
|
|
146
26
|
try {
|
|
147
|
-
const {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
// Handle new response format: response.data.tokens
|
|
159
|
-
if (response.success && response.data && response.data.tokens) {
|
|
160
|
-
const { tokens } = response.data
|
|
161
|
-
const tokenData = {
|
|
162
|
-
access_token: tokens.accessToken,
|
|
163
|
-
refresh_token: tokens.refreshToken,
|
|
164
|
-
expires_in: tokens.accessTokenExp?.expiresIn,
|
|
165
|
-
token_type: 'Bearer'
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Set tokens in TokenManager
|
|
169
|
-
if (this._tokenManager) {
|
|
170
|
-
this._tokenManager.setTokens(tokenData)
|
|
27
|
+
const { authToken, appKey } = this._context || {}
|
|
28
|
+
|
|
29
|
+
// Store masked configuration info
|
|
30
|
+
this._info = {
|
|
31
|
+
config: {
|
|
32
|
+
appKey: appKey
|
|
33
|
+
? `${appKey.substr(0, 4)}...${appKey.substr(-4)}`
|
|
34
|
+
: undefined, // eslint-disable-line no-undefined
|
|
35
|
+
hasToken: Boolean(authToken)
|
|
171
36
|
}
|
|
172
37
|
}
|
|
173
38
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
this._clearPluginSession(session)
|
|
177
|
-
}
|
|
178
|
-
return response.data
|
|
179
|
-
}
|
|
180
|
-
throw new Error(response.message)
|
|
39
|
+
this._initialized = true
|
|
40
|
+
this._setReady()
|
|
181
41
|
} catch (error) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
async githubAuth(code, inviteToken = null, options = {}) {
|
|
187
|
-
try {
|
|
188
|
-
const { payload, session } = this._preparePluginPayload({ code }, options.session)
|
|
189
|
-
if (inviteToken) {
|
|
190
|
-
payload.inviteToken = inviteToken
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const response = await this._request('/auth/github', {
|
|
194
|
-
method: 'POST',
|
|
195
|
-
body: JSON.stringify(payload),
|
|
196
|
-
methodName: 'githubAuth'
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
// Handle new response format: response.data.tokens
|
|
200
|
-
if (response.success && response.data && response.data.tokens) {
|
|
201
|
-
const { tokens } = response.data
|
|
202
|
-
const tokenData = {
|
|
203
|
-
access_token: tokens.accessToken,
|
|
204
|
-
refresh_token: tokens.refreshToken,
|
|
205
|
-
expires_in: tokens.accessTokenExp?.expiresIn,
|
|
206
|
-
token_type: 'Bearer'
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Set tokens in TokenManager
|
|
210
|
-
if (this._tokenManager) {
|
|
211
|
-
this._tokenManager.setTokens(tokenData)
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (response.success) {
|
|
216
|
-
if (session) {
|
|
217
|
-
this._clearPluginSession(session)
|
|
218
|
-
}
|
|
219
|
-
return response.data
|
|
220
|
-
}
|
|
221
|
-
throw new Error(response.message)
|
|
222
|
-
} catch (error) {
|
|
223
|
-
throw new Error(`GitHub auth failed: ${error.message}`, { cause: error })
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
async googleAuthCallback (code, redirectUri, inviteToken = null, options = {}) {
|
|
228
|
-
try {
|
|
229
|
-
const { payload: body, session } = this._preparePluginPayload(
|
|
230
|
-
{ code, redirectUri },
|
|
231
|
-
options.session
|
|
232
|
-
)
|
|
233
|
-
if (inviteToken) {
|
|
234
|
-
body.inviteToken = inviteToken
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
const response = await this._request('/auth/google/callback', {
|
|
238
|
-
method: 'POST',
|
|
239
|
-
body: JSON.stringify(body),
|
|
240
|
-
methodName: 'googleAuthCallback'
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
// Handle new response format: response.data.tokens
|
|
244
|
-
if (response.success && response.data && response.data.tokens) {
|
|
245
|
-
const { tokens } = response.data
|
|
246
|
-
const tokenData = {
|
|
247
|
-
access_token: tokens.accessToken,
|
|
248
|
-
refresh_token: tokens.refreshToken,
|
|
249
|
-
expires_in: tokens.accessTokenExp?.expiresIn,
|
|
250
|
-
token_type: 'Bearer'
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Set tokens in TokenManager
|
|
254
|
-
if (this._tokenManager) {
|
|
255
|
-
this._tokenManager.setTokens(tokenData)
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
if (response.success) {
|
|
260
|
-
if (session) {
|
|
261
|
-
this._clearPluginSession(session)
|
|
262
|
-
}
|
|
263
|
-
return response.data
|
|
264
|
-
}
|
|
265
|
-
throw new Error(response.message)
|
|
266
|
-
} catch (error) {
|
|
267
|
-
throw new Error(`Google auth callback failed: ${error.message}`, { cause: error })
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
async requestPasswordReset(email) {
|
|
272
|
-
try {
|
|
273
|
-
const response = await this._request('/auth/request-password-reset', {
|
|
274
|
-
method: 'POST',
|
|
275
|
-
body: JSON.stringify({ email }),
|
|
276
|
-
methodName: 'requestPasswordReset'
|
|
277
|
-
})
|
|
278
|
-
if (response.success) {
|
|
279
|
-
return response.data
|
|
280
|
-
}
|
|
281
|
-
throw new Error(response.message)
|
|
282
|
-
} catch (error) {
|
|
283
|
-
throw new Error(`Password reset request failed: ${error.message}`, { cause: error })
|
|
42
|
+
this._setError(error)
|
|
43
|
+
throw error
|
|
284
44
|
}
|
|
285
45
|
}
|
|
286
46
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
throw new Error(`Password reset confirmation failed: ${error.message}`, { cause: error })
|
|
300
|
-
}
|
|
47
|
+
_requiresInit (methodName) {
|
|
48
|
+
const noInitMethods = new Set([
|
|
49
|
+
'users:login',
|
|
50
|
+
'users:register',
|
|
51
|
+
'users:request-password-reset',
|
|
52
|
+
'users:reset-password',
|
|
53
|
+
'users:reset-password-confirm',
|
|
54
|
+
'users:register-confirmation',
|
|
55
|
+
'users:google-auth',
|
|
56
|
+
'users:github-auth'
|
|
57
|
+
])
|
|
58
|
+
return !noInitMethods.has(methodName)
|
|
301
59
|
}
|
|
302
60
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
method: 'POST',
|
|
307
|
-
body: JSON.stringify({ token }),
|
|
308
|
-
methodName: 'confirmRegistration'
|
|
309
|
-
})
|
|
310
|
-
if (response.success) {
|
|
311
|
-
return response.data
|
|
312
|
-
}
|
|
313
|
-
throw new Error(response.message)
|
|
314
|
-
} catch (error) {
|
|
315
|
-
throw new Error(`Registration confirmation failed: ${error.message}`, { cause: error })
|
|
61
|
+
_requireReady (methodName) {
|
|
62
|
+
if (this._requiresInit(methodName) && !this._initialized) {
|
|
63
|
+
throw new Error('Service not initialized')
|
|
316
64
|
}
|
|
317
65
|
}
|
|
318
66
|
|
|
319
|
-
|
|
320
|
-
this.
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
method: 'POST',
|
|
324
|
-
methodName: 'requestPasswordChange'
|
|
325
|
-
})
|
|
326
|
-
if (response.success) {
|
|
327
|
-
return response.data
|
|
328
|
-
}
|
|
329
|
-
throw new Error(response.message)
|
|
330
|
-
} catch (error) {
|
|
331
|
-
throw new Error(`Password change request failed: ${error.message}`, { cause: error })
|
|
67
|
+
_getBasedService (methodName) {
|
|
68
|
+
const based = this._context.services?.based
|
|
69
|
+
if (this._requiresInit(methodName) && !based) {
|
|
70
|
+
throw new Error('Based service not available')
|
|
332
71
|
}
|
|
72
|
+
return based._client
|
|
333
73
|
}
|
|
334
74
|
|
|
335
|
-
async
|
|
336
|
-
this._requireReady('confirmPasswordChange')
|
|
75
|
+
async login (identifier, password) {
|
|
337
76
|
try {
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
77
|
+
const based = this._getBasedService('login')
|
|
78
|
+
const response = await based.call('users:login', { identifier, password })
|
|
79
|
+
|
|
80
|
+
if (this._initialized) {
|
|
81
|
+
this.updateContext({ authToken: response.token })
|
|
82
|
+
}
|
|
83
|
+
based.setAuthState({
|
|
84
|
+
token: response.token,
|
|
85
|
+
userId: response.userId,
|
|
86
|
+
projectRoles: response.projectRoles,
|
|
87
|
+
globalRole: response.globalRole,
|
|
88
|
+
persistent: true
|
|
342
89
|
})
|
|
343
|
-
if (response.success) {
|
|
344
|
-
return response.data
|
|
345
|
-
}
|
|
346
|
-
throw new Error(response.message)
|
|
347
|
-
} catch (error) {
|
|
348
|
-
throw new Error(`Password change confirmation failed: ${error.message}`, { cause: error })
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
async getMe(options = {}) {
|
|
353
|
-
this._requireReady('getMe')
|
|
354
|
-
try {
|
|
355
|
-
const session = this._resolvePluginSession(options.session)
|
|
356
|
-
const endpoint = session
|
|
357
|
-
? `/auth/me?session=${encodeURIComponent(session)}`
|
|
358
|
-
: '/auth/me'
|
|
359
90
|
|
|
360
|
-
|
|
361
|
-
method: 'GET',
|
|
362
|
-
methodName: 'getMe'
|
|
363
|
-
})
|
|
364
|
-
if (response.success) {
|
|
365
|
-
return response.data
|
|
366
|
-
}
|
|
367
|
-
throw new Error(response.message)
|
|
91
|
+
return response
|
|
368
92
|
} catch (error) {
|
|
369
|
-
throw new Error(`
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
getAuthToken() {
|
|
374
|
-
if (!this._tokenManager) {
|
|
375
|
-
return null
|
|
93
|
+
throw new Error(`Login failed: ${error.message}`)
|
|
376
94
|
}
|
|
377
|
-
return this._tokenManager.getAccessToken()
|
|
378
95
|
}
|
|
379
96
|
|
|
380
|
-
|
|
381
|
-
* Get stored authentication state (backward compatibility method)
|
|
382
|
-
* Replaces AuthService.getStoredAuthState()
|
|
383
|
-
*/
|
|
384
|
-
async getStoredAuthState() {
|
|
97
|
+
async register (userData) {
|
|
385
98
|
try {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
userId: false,
|
|
389
|
-
authToken: false
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
const tokenStatus = this._tokenManager.getTokenStatus()
|
|
394
|
-
|
|
395
|
-
if (!tokenStatus.hasTokens) {
|
|
396
|
-
return {
|
|
397
|
-
userId: false,
|
|
398
|
-
authToken: false
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// If tokens exist but are invalid, try to refresh
|
|
403
|
-
if (!tokenStatus.isValid && tokenStatus.hasRefreshToken) {
|
|
404
|
-
try {
|
|
405
|
-
await this._tokenManager.ensureValidToken()
|
|
406
|
-
} catch (error) {
|
|
407
|
-
console.warn('[AuthService] Token refresh failed:', error.message)
|
|
408
|
-
// Only clear tokens if it's definitely an auth error, not a network error
|
|
409
|
-
if (
|
|
410
|
-
error.message.includes('401') ||
|
|
411
|
-
error.message.includes('403') ||
|
|
412
|
-
error.message.includes('invalid') ||
|
|
413
|
-
error.message.includes('expired')
|
|
414
|
-
) {
|
|
415
|
-
this._tokenManager.clearTokens()
|
|
416
|
-
return {
|
|
417
|
-
userId: false,
|
|
418
|
-
authToken: false,
|
|
419
|
-
error: `Authentication failed: ${error.message}`
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
// For network errors, keep tokens and return what we have
|
|
423
|
-
return {
|
|
424
|
-
userId: false,
|
|
425
|
-
authToken: this._tokenManager.getAccessToken(),
|
|
426
|
-
error: `Network error during token refresh: ${error.message}`,
|
|
427
|
-
hasTokens: true
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// Check if we have a valid token now
|
|
433
|
-
const currentAccessToken = this._tokenManager.getAccessToken()
|
|
434
|
-
if (!currentAccessToken) {
|
|
435
|
-
return {
|
|
436
|
-
userId: false,
|
|
437
|
-
authToken: false
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// Get current user data if we have valid tokens
|
|
442
|
-
// Be more lenient with API failures - don't immediately clear tokens
|
|
443
|
-
try {
|
|
444
|
-
const currentUser = await this.getMe()
|
|
445
|
-
|
|
446
|
-
return {
|
|
447
|
-
userId: currentUser.user.id,
|
|
448
|
-
authToken: currentAccessToken,
|
|
449
|
-
...currentUser,
|
|
450
|
-
error: null
|
|
451
|
-
}
|
|
452
|
-
} catch (error) {
|
|
453
|
-
console.warn('[AuthService] Failed to get user data:', error.message)
|
|
454
|
-
|
|
455
|
-
// Only clear tokens if it's an auth error (401, 403), not network errors
|
|
456
|
-
if (error.message.includes('401') || error.message.includes('403')) {
|
|
457
|
-
this._tokenManager.clearTokens()
|
|
458
|
-
return {
|
|
459
|
-
userId: false,
|
|
460
|
-
authToken: false,
|
|
461
|
-
error: `Authentication failed: ${error.message}`
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// For other errors (network, 500, etc.), keep tokens but return minimal state
|
|
466
|
-
return {
|
|
467
|
-
userId: false,
|
|
468
|
-
authToken: currentAccessToken,
|
|
469
|
-
error: `Failed to get user data: ${error.message}`,
|
|
470
|
-
hasTokens: true
|
|
471
|
-
}
|
|
472
|
-
}
|
|
99
|
+
const based = this._getBasedService('register')
|
|
100
|
+
return await based.call('users:register', userData)
|
|
473
101
|
} catch (error) {
|
|
474
|
-
|
|
475
|
-
'[AuthService] Unexpected error in getStoredAuthState:',
|
|
476
|
-
error
|
|
477
|
-
)
|
|
478
|
-
return {
|
|
479
|
-
userId: false,
|
|
480
|
-
authToken: false,
|
|
481
|
-
error: `Failed to get stored auth state: ${error.message}`
|
|
482
|
-
}
|
|
102
|
+
throw new Error(`Registration failed: ${error.message}`)
|
|
483
103
|
}
|
|
484
104
|
}
|
|
485
105
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
async getUserProfile() {
|
|
489
|
-
this._requireReady('getUserProfile')
|
|
106
|
+
async googleAuth (idToken) {
|
|
490
107
|
try {
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
methodName: 'getUserProfile'
|
|
494
|
-
})
|
|
495
|
-
if (response.success) {
|
|
496
|
-
return response.data
|
|
497
|
-
}
|
|
498
|
-
throw new Error(response.message)
|
|
108
|
+
const based = this._getBasedService('googleAuth')
|
|
109
|
+
return await based.call('users:google-auth', { idToken })
|
|
499
110
|
} catch (error) {
|
|
500
|
-
throw new Error(`
|
|
111
|
+
throw new Error(`Google auth failed: ${error.message}`)
|
|
501
112
|
}
|
|
502
113
|
}
|
|
503
114
|
|
|
504
|
-
async
|
|
505
|
-
this._requireReady('updateUserProfile')
|
|
115
|
+
async githubAuth (code) {
|
|
506
116
|
try {
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
body: JSON.stringify(profileData),
|
|
510
|
-
methodName: 'updateUserProfile'
|
|
511
|
-
})
|
|
512
|
-
if (response.success) {
|
|
513
|
-
return response.data
|
|
514
|
-
}
|
|
515
|
-
throw new Error(response.message)
|
|
117
|
+
const based = this._getBasedService('githubAuth')
|
|
118
|
+
return await based.call('users:github-auth', { code })
|
|
516
119
|
} catch (error) {
|
|
517
|
-
throw new Error(`
|
|
120
|
+
throw new Error(`GitHub auth failed: ${error.message}`)
|
|
518
121
|
}
|
|
519
122
|
}
|
|
520
123
|
|
|
521
|
-
async
|
|
522
|
-
this._requireReady('
|
|
124
|
+
async logout () {
|
|
125
|
+
this._requireReady('logout')
|
|
523
126
|
try {
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
})
|
|
528
|
-
if (response.success) {
|
|
529
|
-
return response.data.map((project) => ({
|
|
530
|
-
...project,
|
|
531
|
-
...(project.icon && {
|
|
532
|
-
icon: {
|
|
533
|
-
src: `${this._apiUrl}/core/files/public/${project.icon.id}/download`,
|
|
534
|
-
...project.icon
|
|
535
|
-
}
|
|
536
|
-
})
|
|
537
|
-
}))
|
|
538
|
-
}
|
|
539
|
-
throw new Error(response.message)
|
|
127
|
+
const based = this._getBasedService('logout')
|
|
128
|
+
await based.call('logout')
|
|
129
|
+
this.updateContext({ authToken: null })
|
|
540
130
|
} catch (error) {
|
|
541
|
-
throw new Error(`
|
|
131
|
+
throw new Error(`Logout failed: ${error.message}`)
|
|
542
132
|
}
|
|
543
133
|
}
|
|
544
134
|
|
|
545
|
-
async
|
|
546
|
-
this._requireReady('
|
|
135
|
+
async updateUserRole (userId, newRole) {
|
|
136
|
+
this._requireReady('updateUserRole')
|
|
547
137
|
if (!userId) {
|
|
548
138
|
throw new Error('User ID is required')
|
|
549
139
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
method: 'GET',
|
|
553
|
-
methodName: 'getUser'
|
|
554
|
-
})
|
|
555
|
-
if (response.success) {
|
|
556
|
-
return response.data
|
|
557
|
-
}
|
|
558
|
-
throw new Error(response.message)
|
|
559
|
-
} catch (error) {
|
|
560
|
-
throw new Error(`Failed to get user: ${error.message}`, { cause: error })
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
async getUserByEmail(email) {
|
|
565
|
-
this._requireReady('getUserByEmail')
|
|
566
|
-
if (!email) {
|
|
567
|
-
throw new Error('Email is required')
|
|
140
|
+
if (!this._userRoles.has(newRole)) {
|
|
141
|
+
throw new Error(`Invalid role: ${newRole}`)
|
|
568
142
|
}
|
|
569
143
|
try {
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
methodName: 'getUserByEmail'
|
|
573
|
-
})
|
|
574
|
-
if (response.success) {
|
|
575
|
-
return response.data.user
|
|
576
|
-
}
|
|
577
|
-
throw new Error(response.message)
|
|
144
|
+
const based = this._getBasedService('updateUserRole')
|
|
145
|
+
return await based.call('users:update-role', { userId, role: newRole })
|
|
578
146
|
} catch (error) {
|
|
579
|
-
throw new Error(`Failed to
|
|
147
|
+
throw new Error(`Failed to update user role: ${error.message}`)
|
|
580
148
|
}
|
|
581
149
|
}
|
|
582
150
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
/**
|
|
586
|
-
* Get the current user's role for a specific project by project ID
|
|
587
|
-
* Uses caching to avoid repeated API calls
|
|
588
|
-
*/
|
|
589
|
-
async getMyProjectRole(projectId) {
|
|
590
|
-
this._requireReady('getMyProjectRole')
|
|
151
|
+
async updateProjectTier (projectId, newTier) {
|
|
152
|
+
this._requireReady('updateProjectTier')
|
|
591
153
|
if (!projectId) {
|
|
592
154
|
throw new Error('Project ID is required')
|
|
593
155
|
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
if (!this.hasValidTokens()) {
|
|
597
|
-
return 'guest'
|
|
156
|
+
if (!this._projectTiers.has(newTier)) {
|
|
157
|
+
throw new Error(`Invalid project tier: ${newTier}`)
|
|
598
158
|
}
|
|
599
|
-
|
|
600
|
-
// Check cache first
|
|
601
|
-
const cacheKey = `role_${projectId}`
|
|
602
|
-
const cached = this._projectRoleCache.get(cacheKey)
|
|
603
|
-
|
|
604
|
-
if (cached && Date.now() - cached.timestamp < this._roleCacheExpiry) {
|
|
605
|
-
return cached.role
|
|
606
|
-
}
|
|
607
|
-
|
|
608
159
|
try {
|
|
609
|
-
const
|
|
610
|
-
|
|
611
|
-
|
|
160
|
+
const based = this._getBasedService('updateProjectTier')
|
|
161
|
+
return await based.call('projects:update-tier', {
|
|
162
|
+
projectId,
|
|
163
|
+
tier: newTier
|
|
612
164
|
})
|
|
613
|
-
|
|
614
|
-
if (response.success) {
|
|
615
|
-
const { role } = response.data
|
|
616
|
-
// Cache the result
|
|
617
|
-
this._projectRoleCache.set(cacheKey, {
|
|
618
|
-
role,
|
|
619
|
-
timestamp: Date.now()
|
|
620
|
-
})
|
|
621
|
-
return role
|
|
622
|
-
}
|
|
623
|
-
throw new Error(response.message)
|
|
624
|
-
} catch (error) {
|
|
625
|
-
const message = error?.message || ''
|
|
626
|
-
// If request failed due to missing/invalid auth, default to guest
|
|
627
|
-
if (/401|403|unauthorized|no token|invalid token/iu.test(message)) {
|
|
628
|
-
return 'guest'
|
|
629
|
-
}
|
|
630
|
-
throw new Error(`Failed to get project role: ${message}`, { cause: error })
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
/**
|
|
635
|
-
* Get the current user's role for a specific project by project key
|
|
636
|
-
* Uses caching to avoid repeated API calls
|
|
637
|
-
*/
|
|
638
|
-
async getMyProjectRoleByKey(projectKey) {
|
|
639
|
-
this._requireReady('getMyProjectRoleByKey')
|
|
640
|
-
if (!projectKey) {
|
|
641
|
-
throw new Error('Project key is required')
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
// If there are no valid tokens, treat user as guest for public access
|
|
645
|
-
if (!this.hasValidTokens()) {
|
|
646
|
-
return 'guest'
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
// Check cache first
|
|
650
|
-
const cacheKey = `role_key_${projectKey}`
|
|
651
|
-
const cached = this._projectRoleCache.get(cacheKey)
|
|
652
|
-
|
|
653
|
-
if (cached && Date.now() - cached.timestamp < this._roleCacheExpiry) {
|
|
654
|
-
return cached.role
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
try {
|
|
658
|
-
const response = await this._request(`/projects/key/${projectKey}/role`, {
|
|
659
|
-
method: 'GET',
|
|
660
|
-
methodName: 'getMyProjectRoleByKey'
|
|
661
|
-
})
|
|
662
|
-
|
|
663
|
-
if (response.success) {
|
|
664
|
-
const { role } = response.data
|
|
665
|
-
// Cache the result
|
|
666
|
-
this._projectRoleCache.set(cacheKey, {
|
|
667
|
-
role,
|
|
668
|
-
timestamp: Date.now()
|
|
669
|
-
})
|
|
670
|
-
return role
|
|
671
|
-
}
|
|
672
|
-
throw new Error(response.message)
|
|
673
165
|
} catch (error) {
|
|
674
|
-
|
|
675
|
-
// If request failed due to missing/invalid auth, default to guest
|
|
676
|
-
if (/401|403|unauthorized|no token|invalid token/iu.test(message)) {
|
|
677
|
-
return 'guest'
|
|
678
|
-
}
|
|
679
|
-
throw new Error(`Failed to get project role by key: ${message}`, { cause: error })
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
/**
|
|
684
|
-
* Clear the project role cache for a specific project or all projects
|
|
685
|
-
*/
|
|
686
|
-
clearProjectRoleCache(projectId = null) {
|
|
687
|
-
if (projectId) {
|
|
688
|
-
// Clear specific project cache
|
|
689
|
-
this._projectRoleCache.delete(`role_${projectId}`)
|
|
690
|
-
// Also clear by key if we have it cached
|
|
691
|
-
for (const [key] of this._projectRoleCache) {
|
|
692
|
-
if (key.startsWith('role_key_')) {
|
|
693
|
-
this._projectRoleCache.delete(key)
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
} else {
|
|
697
|
-
// Clear all cache
|
|
698
|
-
this._projectRoleCache.clear()
|
|
166
|
+
throw new Error(`Failed to update project tier: ${error.message}`)
|
|
699
167
|
}
|
|
700
168
|
}
|
|
701
169
|
|
|
702
|
-
|
|
703
|
-
* Get project role with fallback to user projects list
|
|
704
|
-
* This method tries to get the role from user projects first,
|
|
705
|
-
* then falls back to API call if not found
|
|
706
|
-
*/
|
|
707
|
-
async getProjectRoleWithFallback(projectId, userProjects = null) {
|
|
708
|
-
this._requireReady('getProjectRoleWithFallback')
|
|
709
|
-
if (!projectId) {
|
|
710
|
-
throw new Error('Project ID is required')
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
// First try to find in user projects if provided
|
|
714
|
-
if (userProjects && Array.isArray(userProjects)) {
|
|
715
|
-
const userProject = userProjects.find(p => p.id === projectId)
|
|
716
|
-
if (userProject && userProject.role) {
|
|
717
|
-
return userProject.role
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
// Fallback to API call
|
|
722
|
-
return await this.getMyProjectRole(projectId)
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
/**
|
|
726
|
-
* Get project role with fallback to user projects list (by project key)
|
|
727
|
-
* This method tries to get the role from user projects first,
|
|
728
|
-
* then falls back to API call if not found
|
|
729
|
-
*/
|
|
730
|
-
async getProjectRoleByKeyWithFallback(projectKey, userProjects = null) {
|
|
731
|
-
this._requireReady('getProjectRoleByKeyWithFallback')
|
|
732
|
-
if (!projectKey) {
|
|
733
|
-
throw new Error('Project key is required')
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
// First try to find in user projects if provided
|
|
737
|
-
if (userProjects && Array.isArray(userProjects)) {
|
|
738
|
-
const userProject = userProjects.find(p => p.key === projectKey)
|
|
739
|
-
if (userProject && userProject.role) {
|
|
740
|
-
return userProject.role
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
// Fallback to API call
|
|
745
|
-
return await this.getMyProjectRoleByKey(projectKey)
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
// ==================== AUTH HELPER METHODS ====================
|
|
749
|
-
|
|
750
|
-
/**
|
|
751
|
-
* Debug method to check token status
|
|
752
|
-
*/
|
|
753
|
-
getTokenDebugInfo() {
|
|
754
|
-
if (!this._tokenManager) {
|
|
755
|
-
return {
|
|
756
|
-
tokenManagerExists: false,
|
|
757
|
-
error: 'TokenManager not initialized'
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
const tokenStatus = this._tokenManager.getTokenStatus()
|
|
762
|
-
const { tokens } = this._tokenManager
|
|
763
|
-
|
|
764
|
-
return {
|
|
765
|
-
tokenManagerExists: true,
|
|
766
|
-
tokenStatus,
|
|
767
|
-
hasAccessToken: Boolean(tokens.accessToken),
|
|
768
|
-
hasRefreshToken: Boolean(tokens.refreshToken),
|
|
769
|
-
accessTokenPreview: tokens.accessToken
|
|
770
|
-
? `${tokens.accessToken.substring(0, 20)}...`
|
|
771
|
-
: null,
|
|
772
|
-
expiresAt: tokens.expiresAt,
|
|
773
|
-
timeToExpiry: tokenStatus.timeToExpiry,
|
|
774
|
-
authHeader: this._tokenManager.getAuthHeader()
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
/**
|
|
779
|
-
* Helper method to check if user is authenticated
|
|
780
|
-
*/
|
|
781
|
-
isAuthenticated() {
|
|
782
|
-
if (!this._tokenManager) {
|
|
783
|
-
return false
|
|
784
|
-
}
|
|
785
|
-
return this._tokenManager.hasTokens()
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
/**
|
|
789
|
-
* Helper method to check if user has valid tokens
|
|
790
|
-
*/
|
|
791
|
-
hasValidTokens() {
|
|
792
|
-
if (!this._tokenManager) {
|
|
793
|
-
return false
|
|
794
|
-
}
|
|
795
|
-
return (
|
|
796
|
-
this._tokenManager.hasTokens() && this._tokenManager.isAccessTokenValid()
|
|
797
|
-
)
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
/**
|
|
801
|
-
* Helper method to get current user info
|
|
802
|
-
*/
|
|
803
|
-
async getCurrentUser() {
|
|
804
|
-
try {
|
|
805
|
-
return await this.getMe()
|
|
806
|
-
} catch (error) {
|
|
807
|
-
throw new Error(`Failed to get current user: ${error.message}`, { cause: error })
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
/**
|
|
812
|
-
* Helper method to validate user data for registration
|
|
813
|
-
*/
|
|
814
|
-
validateRegistrationData(userData) {
|
|
815
|
-
const errors = []
|
|
816
|
-
|
|
817
|
-
if (!userData.email || typeof userData.email !== 'string') {
|
|
818
|
-
errors.push('Email is required and must be a string')
|
|
819
|
-
} else if (!this._isValidEmail(userData.email)) {
|
|
820
|
-
errors.push('Email must be a valid email address')
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
if (!userData.password || typeof userData.password !== 'string') {
|
|
824
|
-
errors.push('Password is required and must be a string')
|
|
825
|
-
} else if (userData.password.length < 8) {
|
|
826
|
-
errors.push('Password must be at least 8 characters long')
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
if (userData.username && typeof userData.username !== 'string') {
|
|
830
|
-
errors.push('Username must be a string')
|
|
831
|
-
} else if (userData.username && userData.username.length < 3) {
|
|
832
|
-
errors.push('Username must be at least 3 characters long')
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
return {
|
|
836
|
-
isValid: errors.length === 0,
|
|
837
|
-
errors
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
/**
|
|
842
|
-
* Helper method to register with validation
|
|
843
|
-
*/
|
|
844
|
-
async registerWithValidation(userData, options = {}) {
|
|
845
|
-
const validation = this.validateRegistrationData(userData)
|
|
846
|
-
if (!validation.isValid) {
|
|
847
|
-
throw new Error(`Validation failed: ${validation.errors.join(', ')}`)
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
return await this.register(userData, options)
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
/**
|
|
854
|
-
* Helper method to login with validation
|
|
855
|
-
*/
|
|
856
|
-
async loginWithValidation(email, password, options = {}) {
|
|
857
|
-
if (!email || typeof email !== 'string') {
|
|
858
|
-
throw new Error('Email is required and must be a string')
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
if (!password || typeof password !== 'string') {
|
|
862
|
-
throw new Error('Password is required and must be a string')
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
if (!this._isValidEmail(email)) {
|
|
866
|
-
throw new Error('Email must be a valid email address')
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
return await this.login(email, password, options)
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
/**
|
|
873
|
-
* Private helper to validate email format
|
|
874
|
-
*/
|
|
875
|
-
_isValidEmail(email) {
|
|
876
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/u
|
|
877
|
-
return emailRegex.test(email)
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
// ==================== PERMISSION METHODS (Existing) ====================
|
|
881
|
-
|
|
882
|
-
hasPermission(requiredPermission) {
|
|
170
|
+
hasPermission (requiredPermission) {
|
|
883
171
|
const authState = this._context?.state
|
|
884
172
|
if (!authState) {
|
|
885
173
|
return false
|
|
@@ -895,21 +183,21 @@ export class AuthService extends BaseService {
|
|
|
895
183
|
)
|
|
896
184
|
}
|
|
897
185
|
|
|
898
|
-
hasGlobalPermission(globalRole, requiredPermission) {
|
|
186
|
+
hasGlobalPermission (globalRole, requiredPermission) {
|
|
899
187
|
return ROLE_PERMISSIONS[globalRole]?.includes(requiredPermission) || false
|
|
900
188
|
}
|
|
901
189
|
|
|
902
|
-
checkProjectPermission(projectRole, requiredPermission) {
|
|
190
|
+
checkProjectPermission (projectRole, requiredPermission) {
|
|
903
191
|
return (
|
|
904
192
|
PROJECT_ROLE_PERMISSIONS[projectRole]?.includes(requiredPermission) ||
|
|
905
193
|
false
|
|
906
194
|
)
|
|
907
195
|
}
|
|
908
196
|
|
|
909
|
-
checkProjectFeature(projectTier, feature) {
|
|
197
|
+
checkProjectFeature (projectTier, feature) {
|
|
910
198
|
if (feature.startsWith('aiCopilot') || feature.startsWith('aiChatbot')) {
|
|
911
199
|
const [featureBase] = feature.split(':')
|
|
912
|
-
const tierFeature = TIER_FEATURES[projectTier]?.find(
|
|
200
|
+
const tierFeature = TIER_FEATURES[projectTier]?.find(f =>
|
|
913
201
|
f.startsWith(featureBase)
|
|
914
202
|
)
|
|
915
203
|
if (!tierFeature) {
|
|
@@ -923,7 +211,7 @@ export class AuthService extends BaseService {
|
|
|
923
211
|
}
|
|
924
212
|
|
|
925
213
|
// Operation checking
|
|
926
|
-
async canPerformOperation(projectId, operation, options = {}) {
|
|
214
|
+
async canPerformOperation (projectId, operation, options = {}) {
|
|
927
215
|
this._requireReady()
|
|
928
216
|
if (!projectId) {
|
|
929
217
|
throw new Error('Project ID is required')
|
|
@@ -935,14 +223,17 @@ export class AuthService extends BaseService {
|
|
|
935
223
|
if (!operationConfig) {
|
|
936
224
|
return false
|
|
937
225
|
}
|
|
226
|
+
if (!operationConfig) {
|
|
227
|
+
return false
|
|
228
|
+
}
|
|
938
229
|
|
|
939
230
|
const { permissions = [], features = [] } = operationConfig
|
|
940
231
|
|
|
941
232
|
try {
|
|
942
233
|
// Check permissions
|
|
943
234
|
const permissionResults = await Promise.all(
|
|
944
|
-
permissions.map(
|
|
945
|
-
this.
|
|
235
|
+
permissions.map(permission =>
|
|
236
|
+
this.hasProjectPermission(projectId, permission)
|
|
946
237
|
)
|
|
947
238
|
)
|
|
948
239
|
|
|
@@ -953,10 +244,13 @@ export class AuthService extends BaseService {
|
|
|
953
244
|
if (!hasPermissions) {
|
|
954
245
|
return false
|
|
955
246
|
}
|
|
247
|
+
if (!hasPermissions) {
|
|
248
|
+
return false
|
|
249
|
+
}
|
|
956
250
|
|
|
957
251
|
// Check features if required
|
|
958
252
|
if (checkFeatures && features.length > 0) {
|
|
959
|
-
const featureResults = features.map(
|
|
253
|
+
const featureResults = features.map(feature => {
|
|
960
254
|
const result = this.hasProjectFeature(projectId, feature)
|
|
961
255
|
return feature.includes(':')
|
|
962
256
|
? typeof result === 'number' && result > 0
|
|
@@ -970,6 +264,9 @@ export class AuthService extends BaseService {
|
|
|
970
264
|
if (!hasFeatures) {
|
|
971
265
|
return false
|
|
972
266
|
}
|
|
267
|
+
if (!hasFeatures) {
|
|
268
|
+
return false
|
|
269
|
+
}
|
|
973
270
|
}
|
|
974
271
|
|
|
975
272
|
return true
|
|
@@ -980,7 +277,7 @@ export class AuthService extends BaseService {
|
|
|
980
277
|
}
|
|
981
278
|
|
|
982
279
|
// Higher-level permission methods
|
|
983
|
-
async withPermission(projectId, operation, action) {
|
|
280
|
+
async withPermission (projectId, operation, action) {
|
|
984
281
|
this._requireReady()
|
|
985
282
|
if (!projectId) {
|
|
986
283
|
throw new Error('Project ID is required')
|
|
@@ -996,115 +293,223 @@ export class AuthService extends BaseService {
|
|
|
996
293
|
return action()
|
|
997
294
|
}
|
|
998
295
|
|
|
999
|
-
//
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
296
|
+
// Project access information
|
|
297
|
+
async getProjectAccess (projectId) {
|
|
298
|
+
this._requireReady()
|
|
299
|
+
if (!projectId) {
|
|
300
|
+
throw new Error('Project ID is required')
|
|
1004
301
|
}
|
|
1005
|
-
// Clear project role cache
|
|
1006
|
-
this._projectRoleCache.clear()
|
|
1007
|
-
this._setReady(false)
|
|
1008
|
-
}
|
|
1009
302
|
|
|
1010
|
-
|
|
1011
|
-
const target =
|
|
1012
|
-
payload && typeof payload === 'object'
|
|
1013
|
-
? { ...payload }
|
|
1014
|
-
: {}
|
|
303
|
+
const operations = Object.keys(PERMISSION_MAP)
|
|
1015
304
|
|
|
1016
|
-
const
|
|
305
|
+
const access = await Promise.all(
|
|
306
|
+
operations.map(async operation => {
|
|
307
|
+
const allowed = await this.canPerformOperation(projectId, operation)
|
|
308
|
+
const config = PERMISSION_MAP[operation]
|
|
1017
309
|
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
310
|
+
return {
|
|
311
|
+
operation,
|
|
312
|
+
allowed,
|
|
313
|
+
permissions: config.permissions,
|
|
314
|
+
features: config.features,
|
|
315
|
+
aiTokens: operation.startsWith('ai')
|
|
316
|
+
? this._getAITokens(projectId, operation.replace('ai', ''))
|
|
317
|
+
: null
|
|
318
|
+
}
|
|
319
|
+
})
|
|
320
|
+
)
|
|
1022
321
|
|
|
1023
|
-
return {
|
|
322
|
+
return {
|
|
323
|
+
projectId,
|
|
324
|
+
permissions: access.reduce(
|
|
325
|
+
(acc, { operation, ...details }) => ({
|
|
326
|
+
...acc,
|
|
327
|
+
[operation]: details
|
|
328
|
+
}),
|
|
329
|
+
{}
|
|
330
|
+
),
|
|
331
|
+
timestamp: new Date().toISOString()
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// AI token management
|
|
336
|
+
_getAITokens (projectId, featureType) {
|
|
337
|
+
const tokenFeatures = [
|
|
338
|
+
`ai${featureType}:3`,
|
|
339
|
+
`ai${featureType}:5`,
|
|
340
|
+
`ai${featureType}:15`
|
|
341
|
+
]
|
|
342
|
+
|
|
343
|
+
return tokenFeatures.reduce((total, feature) => {
|
|
344
|
+
const tokens = this.hasProjectFeature(projectId, feature)
|
|
345
|
+
return total + (typeof tokens === 'number' ? tokens : 0)
|
|
346
|
+
}, 0)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async getProjectMembers (projectId) {
|
|
350
|
+
this._requireReady('getProjectMembers')
|
|
351
|
+
if (!projectId) {
|
|
352
|
+
throw new Error('Project ID is required')
|
|
353
|
+
}
|
|
354
|
+
try {
|
|
355
|
+
const based = this._getBasedService('getProjectMembers')
|
|
356
|
+
return await based.call('projects:get-members', { projectId })
|
|
357
|
+
} catch (error) {
|
|
358
|
+
throw new Error(`Failed to get project members: ${error.message}`)
|
|
359
|
+
}
|
|
1024
360
|
}
|
|
1025
361
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
362
|
+
async inviteMember (projectId, email, role, name, callbackUrl) {
|
|
363
|
+
this._requireReady('inviteMember')
|
|
364
|
+
if (!projectId) {
|
|
365
|
+
throw new Error('Project ID is required')
|
|
1029
366
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
return this._pluginSession
|
|
367
|
+
if (!email) {
|
|
368
|
+
throw new Error('Email is required')
|
|
1033
369
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
if (optionSession) {
|
|
1037
|
-
return this._cachePluginSession(optionSession)
|
|
370
|
+
if (!callbackUrl || Object.keys(callbackUrl).length === 0) {
|
|
371
|
+
throw new Error('Callback Url is required')
|
|
1038
372
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
if (contextSession) {
|
|
1042
|
-
return this._cachePluginSession(contextSession)
|
|
373
|
+
if (!role || !this._userRoles.has(role)) {
|
|
374
|
+
throw new Error(`Invalid role: ${role}`)
|
|
1043
375
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
try {
|
|
1056
|
-
if (window.localStorage) {
|
|
1057
|
-
const stored = window.localStorage.getItem(PLUGIN_SESSION_STORAGE_KEY)
|
|
1058
|
-
if (stored) {
|
|
1059
|
-
this._pluginSession = stored
|
|
1060
|
-
return stored
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
} catch {
|
|
1064
|
-
// Ignore storage access issues
|
|
1065
|
-
}
|
|
376
|
+
try {
|
|
377
|
+
const based = this._getBasedService('inviteMember')
|
|
378
|
+
return await based.call('projects:invite-member', {
|
|
379
|
+
projectId,
|
|
380
|
+
email,
|
|
381
|
+
role,
|
|
382
|
+
name,
|
|
383
|
+
callbackUrl
|
|
384
|
+
})
|
|
385
|
+
} catch (error) {
|
|
386
|
+
throw new Error(`Failed to invite member: ${error.message}`)
|
|
1066
387
|
}
|
|
1067
|
-
|
|
1068
|
-
return null
|
|
1069
388
|
}
|
|
1070
389
|
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
390
|
+
async acceptInvite (token) {
|
|
391
|
+
this._requireReady('acceptInvite')
|
|
392
|
+
try {
|
|
393
|
+
const based = this._getBasedService('acceptInvite')
|
|
394
|
+
return await based.call('projects:accept-invite', { token })
|
|
395
|
+
} catch (error) {
|
|
396
|
+
throw new Error(`Failed to accept invite: ${error.message}`)
|
|
1074
397
|
}
|
|
398
|
+
}
|
|
1075
399
|
|
|
1076
|
-
|
|
400
|
+
async updateMemberRole (projectId, userId, role) {
|
|
401
|
+
this._requireReady('updateMemberRole')
|
|
402
|
+
if (!projectId) {
|
|
403
|
+
throw new Error('Project ID is required')
|
|
404
|
+
}
|
|
405
|
+
if (!userId) {
|
|
406
|
+
throw new Error('User ID is required')
|
|
407
|
+
}
|
|
408
|
+
if (!this._userRoles.has(role)) {
|
|
409
|
+
throw new Error(`Invalid role: ${role}`)
|
|
410
|
+
}
|
|
411
|
+
try {
|
|
412
|
+
const based = this._getBasedService('updateMemberRole')
|
|
413
|
+
return await based.call('projects:update-member-role', {
|
|
414
|
+
projectId,
|
|
415
|
+
userId,
|
|
416
|
+
role
|
|
417
|
+
})
|
|
418
|
+
} catch (error) {
|
|
419
|
+
throw new Error(`Failed to update member role: ${error.message}`)
|
|
420
|
+
}
|
|
421
|
+
}
|
|
1077
422
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
}
|
|
1083
|
-
} catch {
|
|
1084
|
-
// Ignore storage access issues
|
|
1085
|
-
}
|
|
423
|
+
async removeMember (projectId, userId) {
|
|
424
|
+
this._requireReady('removeMember')
|
|
425
|
+
if (!projectId || !userId) {
|
|
426
|
+
throw new Error('Project ID and user ID are required')
|
|
1086
427
|
}
|
|
428
|
+
try {
|
|
429
|
+
const based = this._getBasedService('removeMember')
|
|
430
|
+
return await based.call('projects:remove-member', { projectId, userId })
|
|
431
|
+
} catch (error) {
|
|
432
|
+
throw new Error(`Failed to remove member: ${error.message}`)
|
|
433
|
+
}
|
|
434
|
+
}
|
|
1087
435
|
|
|
1088
|
-
|
|
436
|
+
async confirmRegistration (token) {
|
|
437
|
+
try {
|
|
438
|
+
const based = this._getBasedService('confirmRegistration')
|
|
439
|
+
return await based.call('users:register-confirmation', { token })
|
|
440
|
+
} catch (error) {
|
|
441
|
+
throw new Error(`Registration confirmation failed: ${error.message}`)
|
|
442
|
+
}
|
|
1089
443
|
}
|
|
1090
444
|
|
|
1091
|
-
|
|
1092
|
-
|
|
445
|
+
async requestPasswordReset (email, callbackUrl) {
|
|
446
|
+
try {
|
|
447
|
+
const based = this._getBasedService('requestPasswordReset')
|
|
448
|
+
return await based.call('users:reset-password', { email, callbackUrl })
|
|
449
|
+
} catch (error) {
|
|
450
|
+
throw new Error(`Password reset request failed: ${error.message}`)
|
|
451
|
+
}
|
|
1093
452
|
}
|
|
1094
453
|
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
454
|
+
async confirmPasswordReset (token, newPassword) {
|
|
455
|
+
try {
|
|
456
|
+
const based = this._getBasedService('confirmPasswordReset')
|
|
457
|
+
return await based.call('users:reset-password-confirm', {
|
|
458
|
+
token,
|
|
459
|
+
newPassword
|
|
460
|
+
})
|
|
461
|
+
} catch (error) {
|
|
462
|
+
throw new Error(`Password reset confirmation failed: ${error.message}`)
|
|
1098
463
|
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
async getStoredAuthState () {
|
|
467
|
+
try {
|
|
468
|
+
const based = this._getBasedService('getStoredAuthState')
|
|
469
|
+
const { authState } = based
|
|
1099
470
|
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
471
|
+
if (authState?.token) {
|
|
472
|
+
return {
|
|
473
|
+
userId: authState.userId,
|
|
474
|
+
authToken: authState.token,
|
|
475
|
+
projectRoles: authState.projectRoles,
|
|
476
|
+
globalRole: authState.globalRole,
|
|
477
|
+
error: null
|
|
1104
478
|
}
|
|
1105
|
-
}
|
|
1106
|
-
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return {
|
|
482
|
+
userId: false,
|
|
483
|
+
authToken: false
|
|
484
|
+
}
|
|
485
|
+
} catch (error) {
|
|
486
|
+
this._setError(error)
|
|
487
|
+
return {
|
|
488
|
+
userId: false,
|
|
489
|
+
authToken: false,
|
|
490
|
+
error: `Failed to get stored auth state: ${error.message}`
|
|
1107
491
|
}
|
|
1108
492
|
}
|
|
1109
493
|
}
|
|
494
|
+
|
|
495
|
+
async subscribeToAuthChanges (callback) {
|
|
496
|
+
const based = this._getBasedService('subscribeToAuthChanges')
|
|
497
|
+
based.on('authstate-change', async authState => {
|
|
498
|
+
const formattedState = authState?.token
|
|
499
|
+
? {
|
|
500
|
+
userId: authState.userId,
|
|
501
|
+
authToken: authState.token,
|
|
502
|
+
projectRoles: authState.projectRoles,
|
|
503
|
+
globalRole: authState.globalRole,
|
|
504
|
+
error: null
|
|
505
|
+
}
|
|
506
|
+
: {
|
|
507
|
+
userId: false,
|
|
508
|
+
authToken: false
|
|
509
|
+
}
|
|
510
|
+
await callback(formattedState)
|
|
511
|
+
})
|
|
512
|
+
|
|
513
|
+
return () => based.off('authstate-change')
|
|
514
|
+
}
|
|
1110
515
|
}
|