@symbo.ls/sdk 3.2.3 → 3.2.7
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 +141 -0
- package/dist/cjs/config/environment.js +94 -10
- package/dist/cjs/index.js +152 -12
- package/dist/cjs/services/AdminService.js +351 -0
- package/dist/cjs/services/AuthService.js +738 -305
- package/dist/cjs/services/BaseService.js +158 -6
- package/dist/cjs/services/BranchService.js +484 -0
- package/dist/cjs/services/CollabService.js +439 -116
- package/dist/cjs/services/DnsService.js +340 -0
- package/dist/cjs/services/FeatureFlagService.js +175 -0
- package/dist/cjs/services/FileService.js +201 -0
- package/dist/cjs/services/IntegrationService.js +538 -0
- package/dist/cjs/services/MetricsService.js +62 -0
- package/dist/cjs/services/PaymentService.js +271 -0
- package/dist/cjs/services/PlanService.js +426 -0
- package/dist/cjs/services/ProjectService.js +1207 -0
- package/dist/cjs/services/PullRequestService.js +503 -0
- package/dist/cjs/services/ScreenshotService.js +304 -0
- package/dist/cjs/services/SubscriptionService.js +396 -0
- package/dist/cjs/services/TrackingService.js +661 -0
- package/dist/cjs/services/WaitlistService.js +148 -0
- package/dist/cjs/services/index.js +60 -4
- package/dist/cjs/state/RootStateManager.js +2 -23
- package/dist/cjs/state/rootEventBus.js +9 -0
- package/dist/cjs/utils/CollabClient.js +78 -12
- package/dist/cjs/utils/TokenManager.js +16 -3
- package/dist/cjs/utils/changePreprocessor.js +199 -0
- package/dist/cjs/utils/jsonDiff.js +46 -4
- package/dist/cjs/utils/ordering.js +309 -0
- package/dist/cjs/utils/services.js +285 -128
- package/dist/cjs/utils/validation.js +0 -3
- package/dist/esm/config/environment.js +94 -10
- package/dist/esm/index.js +47862 -18248
- package/dist/esm/services/AdminService.js +1132 -0
- package/dist/esm/services/AuthService.js +1493 -386
- package/dist/esm/services/BaseService.js +757 -6
- package/dist/esm/services/BranchService.js +1265 -0
- package/dist/esm/services/CollabService.js +24956 -16089
- package/dist/esm/services/DnsService.js +1121 -0
- package/dist/esm/services/FeatureFlagService.js +956 -0
- package/dist/esm/services/FileService.js +982 -0
- package/dist/esm/services/IntegrationService.js +1319 -0
- package/dist/esm/services/MetricsService.js +843 -0
- package/dist/esm/services/PaymentService.js +1052 -0
- package/dist/esm/services/PlanService.js +1207 -0
- package/dist/esm/services/ProjectService.js +2526 -0
- package/dist/esm/services/PullRequestService.js +1284 -0
- package/dist/esm/services/ScreenshotService.js +1085 -0
- package/dist/esm/services/SubscriptionService.js +1177 -0
- package/dist/esm/services/TrackingService.js +18454 -0
- package/dist/esm/services/WaitlistService.js +929 -0
- package/dist/esm/services/index.js +47373 -18027
- package/dist/esm/state/RootStateManager.js +11 -23
- package/dist/esm/state/rootEventBus.js +9 -0
- package/dist/esm/utils/CollabClient.js +17526 -16120
- package/dist/esm/utils/TokenManager.js +16 -3
- package/dist/esm/utils/changePreprocessor.js +542 -0
- package/dist/esm/utils/jsonDiff.js +958 -43
- package/dist/esm/utils/ordering.js +291 -0
- package/dist/esm/utils/services.js +285 -128
- package/dist/esm/utils/validation.js +116 -50
- package/dist/node/config/environment.js +94 -10
- package/dist/node/index.js +183 -16
- package/dist/node/services/AdminService.js +332 -0
- package/dist/node/services/AuthService.js +742 -310
- package/dist/node/services/BaseService.js +148 -6
- package/dist/node/services/BranchService.js +465 -0
- package/dist/node/services/CollabService.js +439 -116
- package/dist/node/services/DnsService.js +321 -0
- package/dist/node/services/FeatureFlagService.js +156 -0
- package/dist/node/services/FileService.js +182 -0
- package/dist/node/services/IntegrationService.js +519 -0
- package/dist/node/services/MetricsService.js +43 -0
- package/dist/node/services/PaymentService.js +252 -0
- package/dist/node/services/PlanService.js +407 -0
- package/dist/node/services/ProjectService.js +1188 -0
- package/dist/node/services/PullRequestService.js +484 -0
- package/dist/node/services/ScreenshotService.js +285 -0
- package/dist/node/services/SubscriptionService.js +377 -0
- package/dist/node/services/TrackingService.js +632 -0
- package/dist/node/services/WaitlistService.js +129 -0
- package/dist/node/services/index.js +60 -4
- package/dist/node/state/RootStateManager.js +2 -23
- package/dist/node/state/rootEventBus.js +9 -0
- package/dist/node/utils/CollabClient.js +77 -11
- package/dist/node/utils/TokenManager.js +16 -3
- package/dist/node/utils/changePreprocessor.js +180 -0
- package/dist/node/utils/jsonDiff.js +46 -4
- package/dist/node/utils/ordering.js +290 -0
- package/dist/node/utils/services.js +285 -128
- package/dist/node/utils/validation.js +0 -3
- package/package.json +30 -18
- package/src/config/environment.js +95 -10
- package/src/index.js +190 -23
- package/src/services/AdminService.js +374 -0
- package/src/services/AuthService.js +874 -328
- package/src/services/BaseService.js +166 -6
- package/src/services/BranchService.js +536 -0
- package/src/services/CollabService.js +557 -148
- package/src/services/DnsService.js +366 -0
- package/src/services/FeatureFlagService.js +174 -0
- package/src/services/FileService.js +213 -0
- package/src/services/IntegrationService.js +548 -0
- package/src/services/MetricsService.js +40 -0
- package/src/services/PaymentService.js +287 -0
- package/src/services/PlanService.js +468 -0
- package/src/services/ProjectService.js +1366 -0
- package/src/services/PullRequestService.js +537 -0
- package/src/services/ScreenshotService.js +258 -0
- package/src/services/SubscriptionService.js +425 -0
- package/src/services/TrackingService.js +853 -0
- package/src/services/WaitlistService.js +130 -0
- package/src/services/index.js +79 -5
- package/src/services/tests/BranchService/createBranch.test.js +153 -0
- package/src/services/tests/BranchService/deleteBranch.test.js +173 -0
- package/src/services/tests/BranchService/getBranchChanges.test.js +146 -0
- package/src/services/tests/BranchService/listBranches.test.js +87 -0
- package/src/services/tests/BranchService/mergeBranch.test.js +210 -0
- package/src/services/tests/BranchService/publishVersion.test.js +183 -0
- package/src/services/tests/BranchService/renameBranch.test.js +240 -0
- package/src/services/tests/BranchService/resetBranch.test.js +152 -0
- package/src/services/tests/FeatureFlagService/adminFeatureFlags.test.js +67 -0
- package/src/services/tests/FeatureFlagService/getFeatureFlags.test.js +75 -0
- package/src/services/tests/FileService/createFileFormData.test.js +74 -0
- package/src/services/tests/FileService/getFileUrl.test.js +69 -0
- package/src/services/tests/FileService/updateProjectIcon.test.js +109 -0
- package/src/services/tests/FileService/uploadDocument.test.js +36 -0
- package/src/services/tests/FileService/uploadFile.test.js +78 -0
- package/src/services/tests/FileService/uploadFileWithValidation.test.js +114 -0
- package/src/services/tests/FileService/uploadImage.test.js +36 -0
- package/src/services/tests/FileService/uploadMultipleFiles.test.js +111 -0
- package/src/services/tests/FileService/validateFile.test.js +63 -0
- package/src/services/tests/PlanService/createPlan.test.js +104 -0
- package/src/services/tests/PlanService/createPlanWithValidation.test.js +523 -0
- package/src/services/tests/PlanService/deletePlan.test.js +92 -0
- package/src/services/tests/PlanService/getActivePlans.test.js +123 -0
- package/src/services/tests/PlanService/getAdminPlans.test.js +84 -0
- package/src/services/tests/PlanService/getPlan.test.js +50 -0
- package/src/services/tests/PlanService/getPlanByKey.test.js +109 -0
- package/src/services/tests/PlanService/getPlanWithValidation.test.js +85 -0
- package/src/services/tests/PlanService/getPlans.test.js +53 -0
- package/src/services/tests/PlanService/getPlansByPriceRange.test.js +109 -0
- package/src/services/tests/PlanService/getPlansWithValidation.test.js +48 -0
- package/src/services/tests/PlanService/initializePlans.test.js +75 -0
- package/src/services/tests/PlanService/updatePlan.test.js +111 -0
- package/src/services/tests/PlanService/updatePlanWithValidation.test.js +556 -0
- package/src/state/RootStateManager.js +37 -32
- package/src/state/rootEventBus.js +19 -0
- package/src/utils/CollabClient.js +99 -12
- package/src/utils/TokenManager.js +20 -3
- package/src/utils/changePreprocessor.js +239 -0
- package/src/utils/jsonDiff.js +40 -5
- package/src/utils/ordering.js +271 -0
- package/src/utils/services.js +306 -139
- package/src/utils/validation.js +0 -3
- package/dist/cjs/services/AIService.js +0 -155
- package/dist/cjs/services/BasedService.js +0 -1185
- package/dist/cjs/services/CoreService.js +0 -2295
- package/dist/cjs/services/SocketService.js +0 -309
- package/dist/cjs/services/SymstoryService.js +0 -571
- package/dist/cjs/utils/basedQuerys.js +0 -181
- package/dist/cjs/utils/symstoryClient.js +0 -259
- package/dist/esm/services/AIService.js +0 -185
- package/dist/esm/services/BasedService.js +0 -5262
- package/dist/esm/services/CoreService.js +0 -2827
- package/dist/esm/services/SocketService.js +0 -456
- package/dist/esm/services/SymstoryService.js +0 -7025
- package/dist/esm/utils/basedQuerys.js +0 -163
- package/dist/esm/utils/symstoryClient.js +0 -354
- package/dist/node/services/AIService.js +0 -136
- package/dist/node/services/BasedService.js +0 -1156
- package/dist/node/services/CoreService.js +0 -2266
- package/dist/node/services/SocketService.js +0 -280
- package/dist/node/services/SymstoryService.js +0 -542
- package/dist/node/utils/basedQuerys.js +0 -162
- package/dist/node/utils/symstoryClient.js +0 -230
- package/src/services/AIService.js +0 -150
- package/src/services/BasedService.js +0 -1302
- package/src/services/CoreService.js +0 -2548
- package/src/services/SocketService.js +0 -336
- package/src/services/SymstoryService.js +0 -649
- package/src/utils/basedQuerys.js +0 -164
- package/src/utils/symstoryClient.js +0 -252
|
@@ -1,220 +1,885 @@
|
|
|
1
|
-
/* eslint-disable require-await */
|
|
2
1
|
import { BaseService } from './BaseService.js'
|
|
3
2
|
import {
|
|
4
|
-
PERMISSION_MAP,
|
|
5
3
|
ROLE_PERMISSIONS,
|
|
6
4
|
TIER_FEATURES,
|
|
7
5
|
PROJECT_ROLE_PERMISSIONS
|
|
8
6
|
} from '../utils/permission.js'
|
|
9
7
|
|
|
8
|
+
const PLUGIN_SESSION_STORAGE_KEY = 'plugin_auth_session'
|
|
9
|
+
|
|
10
10
|
export class AuthService extends BaseService {
|
|
11
|
-
constructor
|
|
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
|
+
'starter',
|
|
17
17
|
'pro1',
|
|
18
18
|
'pro2',
|
|
19
19
|
'enterprise'
|
|
20
20
|
])
|
|
21
|
-
this.
|
|
21
|
+
this._projectRoleCache = new Map() // Cache for project roles
|
|
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
|
+
)
|
|
22
32
|
}
|
|
23
33
|
|
|
24
|
-
//
|
|
25
|
-
|
|
34
|
+
// Use BaseService.init/_request/_requireReady implementations
|
|
35
|
+
|
|
36
|
+
// ==================== AUTH METHODS ====================
|
|
37
|
+
|
|
38
|
+
async register(userData, options = {}) {
|
|
26
39
|
try {
|
|
27
|
-
const {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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)
|
|
36
53
|
}
|
|
54
|
+
return response.data
|
|
37
55
|
}
|
|
38
|
-
|
|
39
|
-
this._initialized = true
|
|
40
|
-
this._setReady()
|
|
56
|
+
throw new Error(response.message)
|
|
41
57
|
} catch (error) {
|
|
42
|
-
|
|
43
|
-
throw error
|
|
58
|
+
throw new Error(`Registration failed: ${error.message}`, { cause: error })
|
|
44
59
|
}
|
|
45
60
|
}
|
|
46
61
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
+
}
|
|
59
104
|
}
|
|
60
105
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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 })
|
|
64
126
|
}
|
|
65
127
|
}
|
|
66
128
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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 })
|
|
71
142
|
}
|
|
72
|
-
return based._client
|
|
73
143
|
}
|
|
74
144
|
|
|
75
|
-
async
|
|
145
|
+
async googleAuth(idToken, inviteToken = null, options = {}) {
|
|
76
146
|
try {
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
projectRoles: response.projectRoles,
|
|
87
|
-
globalRole: response.globalRole,
|
|
88
|
-
persistent: true
|
|
147
|
+
const { payload, session } = this._preparePluginPayload({ idToken }, options.session)
|
|
148
|
+
if (inviteToken) {
|
|
149
|
+
payload.inviteToken = inviteToken
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const response = await this._request('/auth/google', {
|
|
153
|
+
method: 'POST',
|
|
154
|
+
body: JSON.stringify(payload),
|
|
155
|
+
methodName: 'googleAuth'
|
|
89
156
|
})
|
|
90
157
|
|
|
91
|
-
|
|
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)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (response.success) {
|
|
175
|
+
if (session) {
|
|
176
|
+
this._clearPluginSession(session)
|
|
177
|
+
}
|
|
178
|
+
return response.data
|
|
179
|
+
}
|
|
180
|
+
throw new Error(response.message)
|
|
92
181
|
} catch (error) {
|
|
93
|
-
throw new Error(`
|
|
182
|
+
throw new Error(`Google auth failed: ${error.message}`, { cause: error })
|
|
94
183
|
}
|
|
95
184
|
}
|
|
96
185
|
|
|
97
|
-
async
|
|
186
|
+
async githubAuth(code, inviteToken = null, options = {}) {
|
|
98
187
|
try {
|
|
99
|
-
const
|
|
100
|
-
|
|
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)
|
|
101
222
|
} catch (error) {
|
|
102
|
-
throw new Error(`
|
|
223
|
+
throw new Error(`GitHub auth failed: ${error.message}`, { cause: error })
|
|
103
224
|
}
|
|
104
225
|
}
|
|
105
226
|
|
|
106
|
-
async
|
|
227
|
+
async googleAuthCallback (code, redirectUri, inviteToken = null, options = {}) {
|
|
107
228
|
try {
|
|
108
|
-
const
|
|
109
|
-
|
|
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
|
+
}
|
|
110
252
|
|
|
111
|
-
|
|
112
|
-
this.
|
|
253
|
+
// Set tokens in TokenManager
|
|
254
|
+
if (this._tokenManager) {
|
|
255
|
+
this._tokenManager.setTokens(tokenData)
|
|
256
|
+
}
|
|
113
257
|
}
|
|
114
258
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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'
|
|
119
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 })
|
|
284
|
+
}
|
|
285
|
+
}
|
|
120
286
|
|
|
121
|
-
|
|
287
|
+
async confirmPasswordReset(token, password) {
|
|
288
|
+
try {
|
|
289
|
+
const response = await this._request('/auth/reset-password-confirm', {
|
|
290
|
+
method: 'POST',
|
|
291
|
+
body: JSON.stringify({ token, password }),
|
|
292
|
+
methodName: 'confirmPasswordReset'
|
|
293
|
+
})
|
|
294
|
+
if (response.success) {
|
|
295
|
+
return response.data
|
|
296
|
+
}
|
|
297
|
+
throw new Error(response.message)
|
|
122
298
|
} catch (error) {
|
|
123
|
-
throw new Error(`
|
|
299
|
+
throw new Error(`Password reset confirmation failed: ${error.message}`, { cause: error })
|
|
124
300
|
}
|
|
125
301
|
}
|
|
126
302
|
|
|
127
|
-
async
|
|
303
|
+
async confirmRegistration(token) {
|
|
128
304
|
try {
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
305
|
+
const response = await this._request('/auth/register-confirmation', {
|
|
306
|
+
method: 'POST',
|
|
307
|
+
body: JSON.stringify({ token }),
|
|
308
|
+
methodName: 'confirmRegistration'
|
|
133
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 })
|
|
316
|
+
}
|
|
317
|
+
}
|
|
134
318
|
|
|
135
|
-
|
|
136
|
-
|
|
319
|
+
async requestPasswordChange() {
|
|
320
|
+
this._requireReady('requestPasswordChange')
|
|
321
|
+
try {
|
|
322
|
+
const response = await this._request('/auth/request-password-change', {
|
|
323
|
+
method: 'POST',
|
|
324
|
+
methodName: 'requestPasswordChange'
|
|
325
|
+
})
|
|
326
|
+
if (response.success) {
|
|
327
|
+
return response.data
|
|
137
328
|
}
|
|
329
|
+
throw new Error(response.message)
|
|
330
|
+
} catch (error) {
|
|
331
|
+
throw new Error(`Password change request failed: ${error.message}`, { cause: error })
|
|
332
|
+
}
|
|
333
|
+
}
|
|
138
334
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
335
|
+
async confirmPasswordChange(currentPassword, newPassword, code) {
|
|
336
|
+
this._requireReady('confirmPasswordChange')
|
|
337
|
+
try {
|
|
338
|
+
const response = await this._request('/auth/confirm-password-change', {
|
|
339
|
+
method: 'POST',
|
|
340
|
+
body: JSON.stringify({ currentPassword, newPassword, code }),
|
|
341
|
+
methodName: 'confirmPasswordChange'
|
|
143
342
|
})
|
|
144
|
-
|
|
343
|
+
if (response.success) {
|
|
344
|
+
return response.data
|
|
345
|
+
}
|
|
346
|
+
throw new Error(response.message)
|
|
145
347
|
} catch (error) {
|
|
146
|
-
throw new Error(`
|
|
348
|
+
throw new Error(`Password change confirmation failed: ${error.message}`, { cause: error })
|
|
147
349
|
}
|
|
148
350
|
}
|
|
149
351
|
|
|
150
|
-
async
|
|
352
|
+
async getMe(options = {}) {
|
|
353
|
+
this._requireReady('getMe')
|
|
151
354
|
try {
|
|
152
|
-
const
|
|
153
|
-
const
|
|
355
|
+
const session = this._resolvePluginSession(options.session)
|
|
356
|
+
const endpoint = session
|
|
357
|
+
? `/auth/me?session=${encodeURIComponent(session)}`
|
|
358
|
+
: '/auth/me'
|
|
359
|
+
|
|
360
|
+
const response = await this._request(endpoint, {
|
|
361
|
+
method: 'GET',
|
|
362
|
+
methodName: 'getMe'
|
|
363
|
+
})
|
|
364
|
+
if (response.success) {
|
|
365
|
+
return response.data
|
|
366
|
+
}
|
|
367
|
+
throw new Error(response.message)
|
|
368
|
+
} catch (error) {
|
|
369
|
+
throw new Error(`Failed to get user profile: ${error.message}`, { cause: error })
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
getAuthToken() {
|
|
374
|
+
if (!this._tokenManager) {
|
|
375
|
+
return null
|
|
376
|
+
}
|
|
377
|
+
return this._tokenManager.getAccessToken()
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Get stored authentication state (backward compatibility method)
|
|
382
|
+
* Replaces AuthService.getStoredAuthState()
|
|
383
|
+
*/
|
|
384
|
+
async getStoredAuthState() {
|
|
385
|
+
try {
|
|
386
|
+
if (!this._tokenManager) {
|
|
387
|
+
return {
|
|
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
|
+
}
|
|
154
464
|
|
|
155
|
-
|
|
156
|
-
|
|
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
|
+
}
|
|
473
|
+
} catch (error) {
|
|
474
|
+
console.error(
|
|
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}`
|
|
157
482
|
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
158
485
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
486
|
+
// ==================== USER METHODS ====================
|
|
487
|
+
|
|
488
|
+
async getUserProfile() {
|
|
489
|
+
this._requireReady('getUserProfile')
|
|
490
|
+
try {
|
|
491
|
+
const response = await this._request('/users/profile', {
|
|
492
|
+
method: 'GET',
|
|
493
|
+
methodName: 'getUserProfile'
|
|
163
494
|
})
|
|
495
|
+
if (response.success) {
|
|
496
|
+
return response.data
|
|
497
|
+
}
|
|
498
|
+
throw new Error(response.message)
|
|
499
|
+
} catch (error) {
|
|
500
|
+
throw new Error(`Failed to get user profile: ${error.message}`, { cause: error })
|
|
501
|
+
}
|
|
502
|
+
}
|
|
164
503
|
|
|
165
|
-
|
|
504
|
+
async updateUserProfile(profileData) {
|
|
505
|
+
this._requireReady('updateUserProfile')
|
|
506
|
+
try {
|
|
507
|
+
const response = await this._request('/users/profile', {
|
|
508
|
+
method: 'PATCH',
|
|
509
|
+
body: JSON.stringify(profileData),
|
|
510
|
+
methodName: 'updateUserProfile'
|
|
511
|
+
})
|
|
512
|
+
if (response.success) {
|
|
513
|
+
return response.data
|
|
514
|
+
}
|
|
515
|
+
throw new Error(response.message)
|
|
166
516
|
} catch (error) {
|
|
167
|
-
throw new Error(`
|
|
517
|
+
throw new Error(`Failed to update user profile: ${error.message}`, { cause: error })
|
|
168
518
|
}
|
|
169
519
|
}
|
|
170
520
|
|
|
171
|
-
async
|
|
172
|
-
this._requireReady('
|
|
521
|
+
async getUserProjects() {
|
|
522
|
+
this._requireReady('getUserProjects')
|
|
173
523
|
try {
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
524
|
+
const response = await this._request('/users/projects', {
|
|
525
|
+
method: 'GET',
|
|
526
|
+
methodName: 'getUserProjects'
|
|
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)
|
|
177
540
|
} catch (error) {
|
|
178
|
-
throw new Error(`
|
|
541
|
+
throw new Error(`Failed to get user projects: ${error.message}`, { cause: error })
|
|
179
542
|
}
|
|
180
543
|
}
|
|
181
544
|
|
|
182
|
-
async
|
|
183
|
-
this._requireReady('
|
|
545
|
+
async getUser(userId) {
|
|
546
|
+
this._requireReady('getUser')
|
|
184
547
|
if (!userId) {
|
|
185
548
|
throw new Error('User ID is required')
|
|
186
549
|
}
|
|
187
|
-
|
|
188
|
-
|
|
550
|
+
try {
|
|
551
|
+
const response = await this._request(`/users/${userId}`, {
|
|
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')
|
|
189
568
|
}
|
|
190
569
|
try {
|
|
191
|
-
const
|
|
192
|
-
|
|
570
|
+
const response = await this._request(`/auth/user?email=${email}`, {
|
|
571
|
+
method: 'GET',
|
|
572
|
+
methodName: 'getUserByEmail'
|
|
573
|
+
})
|
|
574
|
+
if (response.success) {
|
|
575
|
+
return response.data.user
|
|
576
|
+
}
|
|
577
|
+
throw new Error(response.message)
|
|
193
578
|
} catch (error) {
|
|
194
|
-
throw new Error(`Failed to
|
|
579
|
+
throw new Error(`Failed to get user by email: ${error.message}`, { cause: error })
|
|
195
580
|
}
|
|
196
581
|
}
|
|
197
582
|
|
|
198
|
-
|
|
199
|
-
|
|
583
|
+
// ==================== PROJECT ROLE METHODS ====================
|
|
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')
|
|
200
591
|
if (!projectId) {
|
|
201
592
|
throw new Error('Project ID is required')
|
|
202
593
|
}
|
|
203
|
-
|
|
204
|
-
|
|
594
|
+
|
|
595
|
+
// If there are no valid tokens, treat user as guest for public access
|
|
596
|
+
if (!this.hasValidTokens()) {
|
|
597
|
+
return 'guest'
|
|
205
598
|
}
|
|
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
|
+
|
|
206
608
|
try {
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
tier: newTier
|
|
609
|
+
const response = await this._request(`/projects/${projectId}/role`, {
|
|
610
|
+
method: 'GET',
|
|
611
|
+
methodName: 'getMyProjectRole'
|
|
211
612
|
})
|
|
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)
|
|
212
624
|
} catch (error) {
|
|
213
|
-
|
|
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 })
|
|
214
631
|
}
|
|
215
632
|
}
|
|
216
633
|
|
|
217
|
-
|
|
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
|
+
} catch (error) {
|
|
674
|
+
const message = error?.message || ''
|
|
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()
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
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) {
|
|
218
883
|
const authState = this._context?.state
|
|
219
884
|
if (!authState) {
|
|
220
885
|
return false
|
|
@@ -230,21 +895,21 @@ export class AuthService extends BaseService {
|
|
|
230
895
|
)
|
|
231
896
|
}
|
|
232
897
|
|
|
233
|
-
hasGlobalPermission
|
|
898
|
+
hasGlobalPermission(globalRole, requiredPermission) {
|
|
234
899
|
return ROLE_PERMISSIONS[globalRole]?.includes(requiredPermission) || false
|
|
235
900
|
}
|
|
236
901
|
|
|
237
|
-
checkProjectPermission
|
|
902
|
+
checkProjectPermission(projectRole, requiredPermission) {
|
|
238
903
|
return (
|
|
239
904
|
PROJECT_ROLE_PERMISSIONS[projectRole]?.includes(requiredPermission) ||
|
|
240
905
|
false
|
|
241
906
|
)
|
|
242
907
|
}
|
|
243
908
|
|
|
244
|
-
checkProjectFeature
|
|
909
|
+
checkProjectFeature(projectTier, feature) {
|
|
245
910
|
if (feature.startsWith('aiCopilot') || feature.startsWith('aiChatbot')) {
|
|
246
911
|
const [featureBase] = feature.split(':')
|
|
247
|
-
const tierFeature = TIER_FEATURES[projectTier]?.find(f =>
|
|
912
|
+
const tierFeature = TIER_FEATURES[projectTier]?.find((f) =>
|
|
248
913
|
f.startsWith(featureBase)
|
|
249
914
|
)
|
|
250
915
|
if (!tierFeature) {
|
|
@@ -258,7 +923,7 @@ export class AuthService extends BaseService {
|
|
|
258
923
|
}
|
|
259
924
|
|
|
260
925
|
// Operation checking
|
|
261
|
-
async canPerformOperation
|
|
926
|
+
async canPerformOperation(projectId, operation, options = {}) {
|
|
262
927
|
this._requireReady()
|
|
263
928
|
if (!projectId) {
|
|
264
929
|
throw new Error('Project ID is required')
|
|
@@ -270,17 +935,14 @@ export class AuthService extends BaseService {
|
|
|
270
935
|
if (!operationConfig) {
|
|
271
936
|
return false
|
|
272
937
|
}
|
|
273
|
-
if (!operationConfig) {
|
|
274
|
-
return false
|
|
275
|
-
}
|
|
276
938
|
|
|
277
939
|
const { permissions = [], features = [] } = operationConfig
|
|
278
940
|
|
|
279
941
|
try {
|
|
280
942
|
// Check permissions
|
|
281
943
|
const permissionResults = await Promise.all(
|
|
282
|
-
permissions.map(permission =>
|
|
283
|
-
this.
|
|
944
|
+
permissions.map((permission) =>
|
|
945
|
+
this.checkProjectPermission(projectId, permission)
|
|
284
946
|
)
|
|
285
947
|
)
|
|
286
948
|
|
|
@@ -291,13 +953,10 @@ export class AuthService extends BaseService {
|
|
|
291
953
|
if (!hasPermissions) {
|
|
292
954
|
return false
|
|
293
955
|
}
|
|
294
|
-
if (!hasPermissions) {
|
|
295
|
-
return false
|
|
296
|
-
}
|
|
297
956
|
|
|
298
957
|
// Check features if required
|
|
299
958
|
if (checkFeatures && features.length > 0) {
|
|
300
|
-
const featureResults = features.map(feature => {
|
|
959
|
+
const featureResults = features.map((feature) => {
|
|
301
960
|
const result = this.hasProjectFeature(projectId, feature)
|
|
302
961
|
return feature.includes(':')
|
|
303
962
|
? typeof result === 'number' && result > 0
|
|
@@ -311,9 +970,6 @@ export class AuthService extends BaseService {
|
|
|
311
970
|
if (!hasFeatures) {
|
|
312
971
|
return false
|
|
313
972
|
}
|
|
314
|
-
if (!hasFeatures) {
|
|
315
|
-
return false
|
|
316
|
-
}
|
|
317
973
|
}
|
|
318
974
|
|
|
319
975
|
return true
|
|
@@ -324,7 +980,7 @@ export class AuthService extends BaseService {
|
|
|
324
980
|
}
|
|
325
981
|
|
|
326
982
|
// Higher-level permission methods
|
|
327
|
-
async withPermission
|
|
983
|
+
async withPermission(projectId, operation, action) {
|
|
328
984
|
this._requireReady()
|
|
329
985
|
if (!projectId) {
|
|
330
986
|
throw new Error('Project ID is required')
|
|
@@ -340,225 +996,115 @@ export class AuthService extends BaseService {
|
|
|
340
996
|
return action()
|
|
341
997
|
}
|
|
342
998
|
|
|
343
|
-
//
|
|
344
|
-
|
|
345
|
-
this.
|
|
346
|
-
|
|
347
|
-
|
|
999
|
+
// Cleanup
|
|
1000
|
+
destroy() {
|
|
1001
|
+
if (this._tokenManager) {
|
|
1002
|
+
this._tokenManager.destroy()
|
|
1003
|
+
this._tokenManager = null
|
|
348
1004
|
}
|
|
1005
|
+
// Clear project role cache
|
|
1006
|
+
this._projectRoleCache.clear()
|
|
1007
|
+
this._setReady(false)
|
|
1008
|
+
}
|
|
349
1009
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
const config = PERMISSION_MAP[operation]
|
|
1010
|
+
_preparePluginPayload(payload, sessionOverride = null) {
|
|
1011
|
+
const target =
|
|
1012
|
+
payload && typeof payload === 'object'
|
|
1013
|
+
? { ...payload }
|
|
1014
|
+
: {}
|
|
356
1015
|
|
|
357
|
-
|
|
358
|
-
operation,
|
|
359
|
-
allowed,
|
|
360
|
-
permissions: config.permissions,
|
|
361
|
-
features: config.features,
|
|
362
|
-
aiTokens: operation.startsWith('ai')
|
|
363
|
-
? this._getAITokens(projectId, operation.replace('ai', ''))
|
|
364
|
-
: null
|
|
365
|
-
}
|
|
366
|
-
})
|
|
367
|
-
)
|
|
1016
|
+
const session = this._resolvePluginSession(sessionOverride)
|
|
368
1017
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
(acc, { operation, ...details }) => ({
|
|
373
|
-
...acc,
|
|
374
|
-
[operation]: details
|
|
375
|
-
}),
|
|
376
|
-
{}
|
|
377
|
-
),
|
|
378
|
-
timestamp: new Date().toISOString()
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// AI token management
|
|
383
|
-
_getAITokens (projectId, featureType) {
|
|
384
|
-
const tokenFeatures = [
|
|
385
|
-
`ai${featureType}:3`,
|
|
386
|
-
`ai${featureType}:5`,
|
|
387
|
-
`ai${featureType}:15`
|
|
388
|
-
]
|
|
389
|
-
|
|
390
|
-
return tokenFeatures.reduce((total, feature) => {
|
|
391
|
-
const tokens = this.hasProjectFeature(projectId, feature)
|
|
392
|
-
return total + (typeof tokens === 'number' ? tokens : 0)
|
|
393
|
-
}, 0)
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
async getProjectMembers (projectId) {
|
|
397
|
-
this._requireReady('getProjectMembers')
|
|
398
|
-
if (!projectId) {
|
|
399
|
-
throw new Error('Project ID is required')
|
|
400
|
-
}
|
|
401
|
-
try {
|
|
402
|
-
const based = this._getBasedService('getProjectMembers')
|
|
403
|
-
return await based.call('projects:get-members', { projectId })
|
|
404
|
-
} catch (error) {
|
|
405
|
-
if (error.message?.includes('Authentication failed. Please try again'))
|
|
406
|
-
window.location.reload()
|
|
407
|
-
throw new Error(`Failed to get project members: ${error.message}`)
|
|
1018
|
+
if (session && !Object.hasOwn(target, 'session')) {
|
|
1019
|
+
target.session = session
|
|
1020
|
+
return { payload: target, session }
|
|
408
1021
|
}
|
|
409
|
-
}
|
|
410
1022
|
|
|
411
|
-
|
|
412
|
-
this._requireReady('inviteMember')
|
|
413
|
-
if (!projectId) {
|
|
414
|
-
throw new Error('Project ID is required')
|
|
415
|
-
}
|
|
416
|
-
if (!email) {
|
|
417
|
-
throw new Error('Email is required')
|
|
418
|
-
}
|
|
419
|
-
if (!callbackUrl || Object.keys(callbackUrl).length === 0) {
|
|
420
|
-
throw new Error('Callback Url is required')
|
|
421
|
-
}
|
|
422
|
-
if (!role || !this._userRoles.has(role)) {
|
|
423
|
-
throw new Error(`Invalid role: ${role}`)
|
|
424
|
-
}
|
|
425
|
-
try {
|
|
426
|
-
const based = this._getBasedService('inviteMember')
|
|
427
|
-
return await based.call('projects:invite-member', {
|
|
428
|
-
projectId,
|
|
429
|
-
email,
|
|
430
|
-
role,
|
|
431
|
-
name,
|
|
432
|
-
callbackUrl
|
|
433
|
-
})
|
|
434
|
-
} catch (error) {
|
|
435
|
-
throw new Error(`Failed to invite member: ${error.message}`)
|
|
436
|
-
}
|
|
1023
|
+
return { payload: target, session: null }
|
|
437
1024
|
}
|
|
438
1025
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
const based = this._getBasedService('acceptInvite')
|
|
443
|
-
return await based.call('projects:accept-invite', { token })
|
|
444
|
-
} catch (error) {
|
|
445
|
-
throw new Error(`Failed to accept invite: ${error.message}`)
|
|
1026
|
+
_resolvePluginSession(sessionOverride = null) {
|
|
1027
|
+
if (sessionOverride) {
|
|
1028
|
+
return this._cachePluginSession(sessionOverride)
|
|
446
1029
|
}
|
|
447
|
-
}
|
|
448
1030
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
if (!projectId) {
|
|
452
|
-
throw new Error('Project ID is required')
|
|
453
|
-
}
|
|
454
|
-
if (!userId) {
|
|
455
|
-
throw new Error('User ID is required')
|
|
456
|
-
}
|
|
457
|
-
if (!this._userRoles.has(role)) {
|
|
458
|
-
throw new Error(`Invalid role: ${role}`)
|
|
1031
|
+
if (this._pluginSession) {
|
|
1032
|
+
return this._pluginSession
|
|
459
1033
|
}
|
|
460
|
-
try {
|
|
461
|
-
const based = this._getBasedService('updateMemberRole')
|
|
462
|
-
return await based.call('projects:update-member-role', {
|
|
463
|
-
projectId,
|
|
464
|
-
userId,
|
|
465
|
-
role
|
|
466
|
-
})
|
|
467
|
-
} catch (error) {
|
|
468
|
-
throw new Error(`Failed to update member role: ${error.message}`)
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
1034
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
throw new Error('Project ID and user ID are required')
|
|
1035
|
+
const optionSession = this._options?.pluginSession
|
|
1036
|
+
if (optionSession) {
|
|
1037
|
+
return this._cachePluginSession(optionSession)
|
|
476
1038
|
}
|
|
477
|
-
try {
|
|
478
|
-
const based = this._getBasedService('removeMember')
|
|
479
|
-
return await based.call('projects:remove-member', { projectId, userId })
|
|
480
|
-
} catch (error) {
|
|
481
|
-
throw new Error(`Failed to remove member: ${error.message}`)
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
1039
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
return await based.call('users:register-confirmation', { token })
|
|
489
|
-
} catch (error) {
|
|
490
|
-
throw new Error(`Registration confirmation failed: ${error.message}`)
|
|
1040
|
+
const contextSession = this._context?.pluginSession
|
|
1041
|
+
if (contextSession) {
|
|
1042
|
+
return this._cachePluginSession(contextSession)
|
|
491
1043
|
}
|
|
492
|
-
}
|
|
493
1044
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
1045
|
+
if (typeof window !== 'undefined') {
|
|
1046
|
+
try {
|
|
1047
|
+
const sessionFromUrl = new URL(window.location.href).searchParams.get('session')
|
|
1048
|
+
if (sessionFromUrl) {
|
|
1049
|
+
return this._cachePluginSession(sessionFromUrl)
|
|
1050
|
+
}
|
|
1051
|
+
} catch {
|
|
1052
|
+
// Ignore URL parsing errors
|
|
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
|
+
}
|
|
500
1066
|
}
|
|
1067
|
+
|
|
1068
|
+
return null
|
|
501
1069
|
}
|
|
502
1070
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
return await based.call('users:reset-password-confirm', {
|
|
507
|
-
token,
|
|
508
|
-
newPassword
|
|
509
|
-
})
|
|
510
|
-
} catch (error) {
|
|
511
|
-
throw new Error(`Password reset confirmation failed: ${error.message}`)
|
|
1071
|
+
_cachePluginSession(session) {
|
|
1072
|
+
if (!session) {
|
|
1073
|
+
return null
|
|
512
1074
|
}
|
|
513
|
-
}
|
|
514
1075
|
|
|
515
|
-
|
|
516
|
-
try {
|
|
517
|
-
const based = this._getBasedService('getStoredAuthState')
|
|
518
|
-
const { authState } = based
|
|
1076
|
+
this._pluginSession = session
|
|
519
1077
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
projectRoles: authState.projectRoles,
|
|
525
|
-
globalRole: authState.globalRole,
|
|
526
|
-
error: null
|
|
1078
|
+
if (typeof window !== 'undefined') {
|
|
1079
|
+
try {
|
|
1080
|
+
if (window.localStorage) {
|
|
1081
|
+
window.localStorage.setItem(PLUGIN_SESSION_STORAGE_KEY, session)
|
|
527
1082
|
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
return {
|
|
531
|
-
userId: false,
|
|
532
|
-
authToken: false
|
|
533
|
-
}
|
|
534
|
-
} catch (error) {
|
|
535
|
-
this._setError(error)
|
|
536
|
-
return {
|
|
537
|
-
userId: false,
|
|
538
|
-
authToken: false,
|
|
539
|
-
error: `Failed to get stored auth state: ${error.message}`
|
|
1083
|
+
} catch {
|
|
1084
|
+
// Ignore storage access issues
|
|
540
1085
|
}
|
|
541
1086
|
}
|
|
1087
|
+
|
|
1088
|
+
return session
|
|
542
1089
|
}
|
|
543
1090
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
const formattedState = authState?.token
|
|
548
|
-
? {
|
|
549
|
-
userId: authState.userId,
|
|
550
|
-
authToken: authState.token,
|
|
551
|
-
projectRoles: authState.projectRoles,
|
|
552
|
-
globalRole: authState.globalRole,
|
|
553
|
-
error: null
|
|
554
|
-
}
|
|
555
|
-
: {
|
|
556
|
-
userId: false,
|
|
557
|
-
authToken: false
|
|
558
|
-
}
|
|
559
|
-
await callback(formattedState)
|
|
560
|
-
})
|
|
1091
|
+
setPluginSession(session) {
|
|
1092
|
+
this._cachePluginSession(session)
|
|
1093
|
+
}
|
|
561
1094
|
|
|
562
|
-
|
|
1095
|
+
_clearPluginSession(session = null) {
|
|
1096
|
+
if (!session || this._pluginSession === session) {
|
|
1097
|
+
this._pluginSession = null
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
if (typeof window !== 'undefined') {
|
|
1101
|
+
try {
|
|
1102
|
+
if (window.localStorage) {
|
|
1103
|
+
window.localStorage.removeItem(PLUGIN_SESSION_STORAGE_KEY)
|
|
1104
|
+
}
|
|
1105
|
+
} catch {
|
|
1106
|
+
// Ignore storage access issues
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
563
1109
|
}
|
|
564
1110
|
}
|