@solidxai/core 0.1.10-beta.27 → 0.1.10-beta.28
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/MICROSOFT_ACTIVE_DIRECTORY_OAUTH_FLOW.md +616 -0
- package/dist/constants/error-messages.d.ts +1 -0
- package/dist/constants/error-messages.d.ts.map +1 -1
- package/dist/constants/error-messages.js +1 -0
- package/dist/constants/error-messages.js.map +1 -1
- package/dist/controllers/microsoft-active-directory-authentication.controller.d.ts +28 -0
- package/dist/controllers/microsoft-active-directory-authentication.controller.d.ts.map +1 -0
- package/dist/controllers/microsoft-active-directory-authentication.controller.js +122 -0
- package/dist/controllers/microsoft-active-directory-authentication.controller.js.map +1 -0
- package/dist/entities/user.entity.d.ts +3 -0
- package/dist/entities/user.entity.d.ts.map +1 -1
- package/dist/entities/user.entity.js +13 -1
- package/dist/entities/user.entity.js.map +1 -1
- package/dist/helpers/microsoft-active-directory-oauth.helper.d.ts +34 -0
- package/dist/helpers/microsoft-active-directory-oauth.helper.d.ts.map +1 -0
- package/dist/helpers/microsoft-active-directory-oauth.helper.js +43 -0
- package/dist/helpers/microsoft-active-directory-oauth.helper.js.map +1 -0
- package/dist/helpers/user-helper.d.ts.map +1 -1
- package/dist/helpers/user-helper.js +4 -0
- package/dist/helpers/user-helper.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/passport-strategies/microsoft-active-directory-oauth.strategy.d.ts +14 -0
- package/dist/passport-strategies/microsoft-active-directory-oauth.strategy.d.ts.map +1 -0
- package/dist/passport-strategies/microsoft-active-directory-oauth.strategy.js +67 -0
- package/dist/passport-strategies/microsoft-active-directory-oauth.strategy.js.map +1 -0
- package/dist/services/authentication.service.d.ts +12 -0
- package/dist/services/authentication.service.d.ts.map +1 -1
- package/dist/services/authentication.service.js +64 -0
- package/dist/services/authentication.service.js.map +1 -1
- package/dist/services/settings/default-settings-provider.service.d.ts +100 -0
- package/dist/services/settings/default-settings-provider.service.d.ts.map +1 -1
- package/dist/services/settings/default-settings-provider.service.js +56 -0
- package/dist/services/settings/default-settings-provider.service.js.map +1 -1
- package/dist/services/user.service.d.ts +2 -0
- package/dist/services/user.service.d.ts.map +1 -1
- package/dist/services/user.service.js +46 -0
- package/dist/services/user.service.js.map +1 -1
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +4 -0
- package/dist/solid-core.module.js.map +1 -1
- package/package.json +1 -1
- package/src/constants/error-messages.ts +2 -1
- package/src/controllers/microsoft-active-directory-authentication.controller.ts +104 -0
- package/src/entities/user.entity.ts +13 -1
- package/src/helpers/microsoft-active-directory-oauth.helper.ts +83 -0
- package/src/helpers/user-helper.ts +5 -1
- package/src/index.ts +1 -0
- package/src/passport-strategies/microsoft-active-directory-oauth.strategy.ts +63 -0
- package/src/services/authentication.service.ts +78 -0
- package/src/services/settings/default-settings-provider.service.ts +59 -0
- package/src/services/user.service.ts +61 -0
- package/src/solid-core.module.ts +4 -0
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
# Microsoft Active Directory OAuth Flow
|
|
2
|
+
|
|
3
|
+
This document explains how Microsoft Active Directory, also called Azure AD or Microsoft Entra ID, OAuth login works in Solid Core and Solid Core UI.
|
|
4
|
+
|
|
5
|
+
## Important Short Answer
|
|
6
|
+
|
|
7
|
+
On button click, frontend does not call the backend with `axios` or `fetch`.
|
|
8
|
+
|
|
9
|
+
Frontend only builds this URL:
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
const backendApiUrl = (
|
|
13
|
+
env("NEXT_PUBLIC_BACKEND_API_URL") || env("API_URL")
|
|
14
|
+
).replace(/\/+$/, "");
|
|
15
|
+
|
|
16
|
+
const getOAuthConnectUrl = (provider: string) =>
|
|
17
|
+
`${backendApiUrl}/api/iam/${provider}/connect`;
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
For Microsoft Active Directory, this becomes:
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
http://localhost:3000/api/iam/microsoft-active-directory/connect
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Then frontend does:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
router.push(getOAuthConnectUrl("microsoft-active-directory"));
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
So frontend creates a URL and navigates the browser to that URL. Browser navigation itself makes the `GET /connect` request.
|
|
33
|
+
|
|
34
|
+
This is correct for OAuth because OAuth needs full browser redirects:
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
Frontend page
|
|
38
|
+
-> Backend /connect
|
|
39
|
+
-> Microsoft login page
|
|
40
|
+
-> Backend /connect/callback
|
|
41
|
+
-> Frontend callback page
|
|
42
|
+
-> Backend /authenticate
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Why It Is Not A Normal API Call
|
|
46
|
+
|
|
47
|
+
Normal login with username/password can use an API call:
|
|
48
|
+
|
|
49
|
+
```text
|
|
50
|
+
frontend axios/fetch -> backend -> response JSON
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
OAuth login cannot work like that for the first step because the user must leave our app and open Microsoft login in the browser.
|
|
54
|
+
|
|
55
|
+
So the first step is navigation:
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
browser location changes to backend /connect
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
After Microsoft login completes, the final token exchange uses an API call:
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
frontend -> backend /authenticate?accessCode=...
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Files Involved
|
|
68
|
+
|
|
69
|
+
Backend files:
|
|
70
|
+
|
|
71
|
+
```text
|
|
72
|
+
src/controllers/microsoft-active-directory-authentication.controller.ts
|
|
73
|
+
src/passport-strategies/microsoft-active-directory-oauth.strategy.ts
|
|
74
|
+
src/helpers/microsoft-active-directory-oauth.helper.ts
|
|
75
|
+
src/services/user.service.ts
|
|
76
|
+
src/services/authentication.service.ts
|
|
77
|
+
src/services/settings/default-settings-provider.service.ts
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Frontend files:
|
|
81
|
+
|
|
82
|
+
```text
|
|
83
|
+
src/components/common/SocialMediaLogin.tsx
|
|
84
|
+
src/routes/pages/auth/InitiateMicrosoftActiveDirectoryOauthPage.tsx
|
|
85
|
+
src/components/auth/MicrosoftActiveDirectoryAuthChecking.tsx
|
|
86
|
+
src/adapters/auth/signInWithOAuthAccessCode.ts
|
|
87
|
+
src/routes/solidRoutes.tsx
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Backend Endpoints
|
|
91
|
+
|
|
92
|
+
### 1. Start Microsoft Active Directory OAuth
|
|
93
|
+
|
|
94
|
+
```http
|
|
95
|
+
GET /api/iam/microsoft-active-directory/connect
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Controller method:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
@Public()
|
|
102
|
+
@UseGuards(MicrosoftActiveDirectoryOauthGuard)
|
|
103
|
+
@Get("connect")
|
|
104
|
+
async connect() {
|
|
105
|
+
await this.validateConfiguration();
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
This endpoint starts OAuth. The guard runs before the method body.
|
|
110
|
+
|
|
111
|
+
The guard:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
export class MicrosoftActiveDirectoryOauthGuard
|
|
115
|
+
extends AuthGuard("microsoft-active-directory") {}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
This tells Passport to use the strategy named:
|
|
119
|
+
|
|
120
|
+
```text
|
|
121
|
+
microsoft-active-directory
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 2. Microsoft Callback
|
|
125
|
+
|
|
126
|
+
```http
|
|
127
|
+
GET /api/iam/microsoft-active-directory/connect/callback
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Controller method:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
@Public()
|
|
134
|
+
@Get("connect/callback")
|
|
135
|
+
@UseGuards(MicrosoftActiveDirectoryOauthGuard)
|
|
136
|
+
async microsoftActiveDirectoryAuthCallback(@Req() req, @Res() res) {
|
|
137
|
+
const config = await this.validateConfiguration();
|
|
138
|
+
const user = req.user;
|
|
139
|
+
|
|
140
|
+
return res.redirect(
|
|
141
|
+
this.buildFrontendRedirectUrl(config.redirectURL, user["accessCode"]),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Microsoft redirects to this endpoint after successful login.
|
|
147
|
+
|
|
148
|
+
This route also uses `MicrosoftActiveDirectoryOauthGuard`, but now Microsoft sends a `code` in the URL. Passport uses that code to get an access token and profile from Microsoft.
|
|
149
|
+
|
|
150
|
+
### 3. Authenticate With accessCode
|
|
151
|
+
|
|
152
|
+
```http
|
|
153
|
+
GET /api/iam/microsoft-active-directory/authenticate?accessCode=<accessCode>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Controller method:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
@Public()
|
|
160
|
+
@Get("authenticate")
|
|
161
|
+
async microsoftActiveDirectoryAuth(@Query("accessCode") accessCode: string) {
|
|
162
|
+
await this.validateConfiguration();
|
|
163
|
+
return this.authService.signInUsingMicrosoftActiveDirectory(accessCode);
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Frontend calls this after it receives `accessCode` from the callback redirect.
|
|
168
|
+
|
|
169
|
+
This returns application JWT tokens.
|
|
170
|
+
|
|
171
|
+
## Complete Request Flow
|
|
172
|
+
|
|
173
|
+
### Step 1: User Clicks Button
|
|
174
|
+
|
|
175
|
+
Frontend file:
|
|
176
|
+
|
|
177
|
+
```text
|
|
178
|
+
src/components/common/SocialMediaLogin.tsx
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Click handler:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
onClick={() => router.push(getOAuthConnectUrl("microsoft-active-directory"))}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
This creates:
|
|
188
|
+
|
|
189
|
+
```text
|
|
190
|
+
http://localhost:3000/api/iam/microsoft-active-directory/connect
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Then browser opens that URL.
|
|
194
|
+
|
|
195
|
+
Note: yahan frontend direct API call nahi kar raha. Sirf URL bana kar browser ko us URL par bhej raha hai.
|
|
196
|
+
|
|
197
|
+
### Step 2: Backend /connect Runs Guard
|
|
198
|
+
|
|
199
|
+
Backend route:
|
|
200
|
+
|
|
201
|
+
```http
|
|
202
|
+
GET /api/iam/microsoft-active-directory/connect
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Before `connect()` runs, Nest runs:
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
MicrosoftActiveDirectoryOauthGuard
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Guard calls Passport strategy:
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
AuthGuard("microsoft-active-directory")
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Strategy config:
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
super({
|
|
221
|
+
clientID,
|
|
222
|
+
clientSecret,
|
|
223
|
+
callbackURL,
|
|
224
|
+
tenant,
|
|
225
|
+
scope: MICROSOFT_ACTIVE_DIRECTORY_OAUTH_SCOPES,
|
|
226
|
+
addUPNAsEmail: true,
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Passport redirects the browser to Microsoft login page.
|
|
231
|
+
|
|
232
|
+
### Step 3: Microsoft Login Page Opens
|
|
233
|
+
|
|
234
|
+
Browser leaves our app and opens Microsoft login.
|
|
235
|
+
|
|
236
|
+
Microsoft receives values like:
|
|
237
|
+
|
|
238
|
+
```text
|
|
239
|
+
client_id
|
|
240
|
+
redirect_uri
|
|
241
|
+
scope
|
|
242
|
+
tenant
|
|
243
|
+
response_type=code
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
The `redirect_uri` comes from:
|
|
247
|
+
|
|
248
|
+
```env
|
|
249
|
+
IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_CALLBACK_URL
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Example:
|
|
253
|
+
|
|
254
|
+
```env
|
|
255
|
+
IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_CALLBACK_URL=http://localhost:3000/api/iam/microsoft-active-directory/connect/callback
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
This exact URL must be registered in Azure App Registration.
|
|
259
|
+
|
|
260
|
+
### Step 4: Microsoft Redirects Back To Backend
|
|
261
|
+
|
|
262
|
+
After successful Microsoft login, Microsoft redirects browser to:
|
|
263
|
+
|
|
264
|
+
```text
|
|
265
|
+
http://localhost:3000/api/iam/microsoft-active-directory/connect/callback?code=...
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
This is the callback route.
|
|
269
|
+
|
|
270
|
+
### Step 5: Guard Runs Again On Callback
|
|
271
|
+
|
|
272
|
+
The callback route also has:
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
@UseGuards(MicrosoftActiveDirectoryOauthGuard)
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Now the guard sees Microsoft `code`.
|
|
279
|
+
|
|
280
|
+
Passport exchanges that `code` with Microsoft and gets:
|
|
281
|
+
|
|
282
|
+
```text
|
|
283
|
+
access token
|
|
284
|
+
profile
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Then Passport calls the strategy `validate()` method.
|
|
288
|
+
|
|
289
|
+
### Step 6: Strategy validate() Creates accessCode
|
|
290
|
+
|
|
291
|
+
Backend file:
|
|
292
|
+
|
|
293
|
+
```text
|
|
294
|
+
src/passport-strategies/microsoft-active-directory-oauth.strategy.ts
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Method:
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
async validate(_accessToken, _refreshToken, profile, done) {
|
|
301
|
+
const loginAccessCode = uuid();
|
|
302
|
+
|
|
303
|
+
const user = {
|
|
304
|
+
provider: "microsoftActiveDirectory",
|
|
305
|
+
providerId,
|
|
306
|
+
email,
|
|
307
|
+
name,
|
|
308
|
+
picture,
|
|
309
|
+
accessCode: loginAccessCode,
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
await this.userService.resolveUserOnOauthMicrosoftActiveDirectory({
|
|
313
|
+
...user,
|
|
314
|
+
accessToken: _accessToken,
|
|
315
|
+
refreshToken: null,
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
done(null, user);
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
This method creates a temporary `accessCode`. This is not the JWT. It is only a one-time code used by frontend to complete login.
|
|
323
|
+
|
|
324
|
+
### Step 7: User Is Created Or Updated
|
|
325
|
+
|
|
326
|
+
Backend file:
|
|
327
|
+
|
|
328
|
+
```text
|
|
329
|
+
src/services/user.service.ts
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Method:
|
|
333
|
+
|
|
334
|
+
```ts
|
|
335
|
+
resolveUserOnOauthMicrosoftActiveDirectory()
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
It checks user by email:
|
|
339
|
+
|
|
340
|
+
```text
|
|
341
|
+
if user does not exist:
|
|
342
|
+
create user
|
|
343
|
+
save Microsoft Active Directory id/token/profile picture
|
|
344
|
+
save accessCode
|
|
345
|
+
initialize default role
|
|
346
|
+
|
|
347
|
+
if user exists:
|
|
348
|
+
update Microsoft Active Directory id/token/profile picture
|
|
349
|
+
update accessCode
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Important fields saved:
|
|
353
|
+
|
|
354
|
+
```text
|
|
355
|
+
accessCode
|
|
356
|
+
microsoftActiveDirectoryId
|
|
357
|
+
microsoftActiveDirectoryAccessToken
|
|
358
|
+
microsoftActiveDirectoryProfilePicture
|
|
359
|
+
lastLoginProvider
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Step 8: Backend Redirects To Frontend
|
|
363
|
+
|
|
364
|
+
After strategy validation, callback controller gets `req.user`.
|
|
365
|
+
|
|
366
|
+
Then it redirects to frontend:
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
this.buildFrontendRedirectUrl(config.redirectURL, user["accessCode"])
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
`config.redirectURL` comes from:
|
|
373
|
+
|
|
374
|
+
```env
|
|
375
|
+
IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_REDIRECT_URL
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Example:
|
|
379
|
+
|
|
380
|
+
```env
|
|
381
|
+
IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_REDIRECT_URL=http://localhost:3001/auth/initiate-microsoft-active-directory-oauth
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Final redirect URL becomes:
|
|
385
|
+
|
|
386
|
+
```text
|
|
387
|
+
http://localhost:3001/auth/initiate-microsoft-active-directory-oauth?accessCode=<uuid>
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
This URL is frontend, not backend.
|
|
391
|
+
|
|
392
|
+
### Step 9: Frontend Reads accessCode
|
|
393
|
+
|
|
394
|
+
Frontend route:
|
|
395
|
+
|
|
396
|
+
```text
|
|
397
|
+
/auth/initiate-microsoft-active-directory-oauth
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
Frontend file:
|
|
401
|
+
|
|
402
|
+
```text
|
|
403
|
+
src/routes/pages/auth/InitiateMicrosoftActiveDirectoryOauthPage.tsx
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
It renders:
|
|
407
|
+
|
|
408
|
+
```ts
|
|
409
|
+
<MicrosoftActiveDirectoryAuthChecking />
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
That component reads:
|
|
413
|
+
|
|
414
|
+
```ts
|
|
415
|
+
const accessCode = searchParams.get("accessCode");
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Step 10: Frontend Calls Backend /authenticate
|
|
419
|
+
|
|
420
|
+
Frontend file:
|
|
421
|
+
|
|
422
|
+
```text
|
|
423
|
+
src/adapters/auth/signInWithOAuthAccessCode.ts
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
It calls:
|
|
427
|
+
|
|
428
|
+
```ts
|
|
429
|
+
solidGet(
|
|
430
|
+
`${apiUrl}/api/iam/${provider}/authenticate?accessCode=${encodeURIComponent(accessCode)}`
|
|
431
|
+
)
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
For Microsoft Active Directory:
|
|
435
|
+
|
|
436
|
+
```text
|
|
437
|
+
GET /api/iam/microsoft-active-directory/authenticate?accessCode=<uuid>
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
This is the real frontend API call.
|
|
441
|
+
|
|
442
|
+
### Step 11: Backend Validates accessCode And Returns JWT
|
|
443
|
+
|
|
444
|
+
Backend file:
|
|
445
|
+
|
|
446
|
+
```text
|
|
447
|
+
src/services/authentication.service.ts
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
Method:
|
|
451
|
+
|
|
452
|
+
```ts
|
|
453
|
+
signInUsingMicrosoftActiveDirectory(accessCode)
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
It does:
|
|
457
|
+
|
|
458
|
+
```text
|
|
459
|
+
1. Find user by accessCode
|
|
460
|
+
2. Check account is not blocked
|
|
461
|
+
3. Validate saved Microsoft access token using Microsoft Graph /me
|
|
462
|
+
4. Verify Microsoft profile id/email matches the saved user
|
|
463
|
+
5. Generate application JWT accessToken and refreshToken
|
|
464
|
+
6. Return user and tokens
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
After frontend receives tokens, it stores session the same way as existing OAuth login.
|
|
468
|
+
|
|
469
|
+
## callbackURL vs redirectURL
|
|
470
|
+
|
|
471
|
+
These two are different and both are required.
|
|
472
|
+
|
|
473
|
+
### callbackURL
|
|
474
|
+
|
|
475
|
+
Used by Microsoft to come back to backend:
|
|
476
|
+
|
|
477
|
+
```env
|
|
478
|
+
IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_CALLBACK_URL=http://localhost:3000/api/iam/microsoft-active-directory/connect/callback
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
This must be registered in Azure App Registration.
|
|
482
|
+
|
|
483
|
+
### redirectURL
|
|
484
|
+
|
|
485
|
+
Used by backend to send the user back to frontend:
|
|
486
|
+
|
|
487
|
+
```env
|
|
488
|
+
IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_REDIRECT_URL=http://localhost:3001/auth/initiate-microsoft-active-directory-oauth
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
This does not go in Azure redirect URI list unless your Azure app directly redirects to frontend, which this backend flow does not do.
|
|
492
|
+
|
|
493
|
+
## Required Environment Variables
|
|
494
|
+
|
|
495
|
+
Backend:
|
|
496
|
+
|
|
497
|
+
```env
|
|
498
|
+
IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_CLIENT_ID=<azure-client-id>
|
|
499
|
+
IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_CLIENT_SECRET=<azure-client-secret>
|
|
500
|
+
IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_TENANT_ID=<tenant-id-or-common>
|
|
501
|
+
IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_CALLBACK_URL=http://localhost:3000/api/iam/microsoft-active-directory/connect/callback
|
|
502
|
+
IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_REDIRECT_URL=http://localhost:3001/auth/initiate-microsoft-active-directory-oauth
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
Frontend:
|
|
506
|
+
|
|
507
|
+
```env
|
|
508
|
+
VITE_BACKEND_API_URL=http://localhost:3000
|
|
509
|
+
VITE_API_URL=http://localhost:3000
|
|
510
|
+
VITE_BASE_URL=http://localhost:3001
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
## Azure App Registration Setup
|
|
514
|
+
|
|
515
|
+
In Azure Portal:
|
|
516
|
+
|
|
517
|
+
```text
|
|
518
|
+
Microsoft Entra ID
|
|
519
|
+
-> App registrations
|
|
520
|
+
-> Your app
|
|
521
|
+
-> Authentication
|
|
522
|
+
-> Add platform
|
|
523
|
+
-> Web
|
|
524
|
+
-> Redirect URIs
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
Add exactly:
|
|
528
|
+
|
|
529
|
+
```text
|
|
530
|
+
http://localhost:3000/api/iam/microsoft-active-directory/connect/callback
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
The value must exactly match `IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_CALLBACK_URL`.
|
|
534
|
+
|
|
535
|
+
Exact means same:
|
|
536
|
+
|
|
537
|
+
```text
|
|
538
|
+
protocol: http vs https
|
|
539
|
+
host: localhost
|
|
540
|
+
port: 3000
|
|
541
|
+
path: /api/iam/microsoft-active-directory/connect/callback
|
|
542
|
+
trailing slash: no extra slash
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
## Common Mistakes
|
|
546
|
+
|
|
547
|
+
### redirect_uri is not valid
|
|
548
|
+
|
|
549
|
+
Reason:
|
|
550
|
+
|
|
551
|
+
```text
|
|
552
|
+
IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_CALLBACK_URL does not match Azure redirect URI.
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
Fix:
|
|
556
|
+
|
|
557
|
+
```text
|
|
558
|
+
Register the exact callback URL in Azure.
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### Using frontend URL as callbackURL
|
|
562
|
+
|
|
563
|
+
Wrong:
|
|
564
|
+
|
|
565
|
+
```env
|
|
566
|
+
IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_CALLBACK_URL=http://localhost:3001/auth/initiate-microsoft-active-directory-oauth
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
Correct:
|
|
570
|
+
|
|
571
|
+
```env
|
|
572
|
+
IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_CALLBACK_URL=http://localhost:3000/api/iam/microsoft-active-directory/connect/callback
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
### Using backend API URL as redirectURL
|
|
576
|
+
|
|
577
|
+
Wrong:
|
|
578
|
+
|
|
579
|
+
```env
|
|
580
|
+
IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_REDIRECT_URL=http://localhost:3000/api/auth/initiate-microsoft-active-directory-oauth
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
Correct:
|
|
584
|
+
|
|
585
|
+
```env
|
|
586
|
+
IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_REDIRECT_URL=http://localhost:3001/auth/initiate-microsoft-active-directory-oauth
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
### Empty client id crashes strategy
|
|
590
|
+
|
|
591
|
+
Wrong:
|
|
592
|
+
|
|
593
|
+
```env
|
|
594
|
+
IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_CLIENT_ID=
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
If OAuth is not configured, leave it absent or let the strategy use dummy startup values. If OAuth should work, set the real Azure client id.
|
|
598
|
+
|
|
599
|
+
## Final Flow Summary
|
|
600
|
+
|
|
601
|
+
```text
|
|
602
|
+
1. User clicks Microsoft Active Directory button
|
|
603
|
+
2. Frontend builds backend /connect URL
|
|
604
|
+
3. Frontend navigates browser to backend /connect
|
|
605
|
+
4. Backend Passport guard redirects browser to Microsoft
|
|
606
|
+
5. User logs in on Microsoft
|
|
607
|
+
6. Microsoft redirects browser to backend /connect/callback with code
|
|
608
|
+
7. Backend exchanges code for Microsoft token/profile
|
|
609
|
+
8. Backend creates/updates user and stores temporary accessCode
|
|
610
|
+
9. Backend redirects browser to frontend redirectURL with accessCode
|
|
611
|
+
10. Frontend reads accessCode
|
|
612
|
+
11. Frontend calls backend /authenticate?accessCode=...
|
|
613
|
+
12. Backend verifies user/token and returns application JWT
|
|
614
|
+
13. Frontend stores JWT session
|
|
615
|
+
```
|
|
616
|
+
|
|
@@ -25,6 +25,7 @@ export declare const ERROR_MESSAGES: {
|
|
|
25
25
|
ACCESS_DENIED: string;
|
|
26
26
|
INVALID_USER_PROFILE: string;
|
|
27
27
|
GOOGLE_OAUTH_PROFILE_FETCH_FAILED: string;
|
|
28
|
+
MICROSOFT_ACTIVE_DIRECTORY_OAUTH_PROFILE_FETCH_FAILED: string;
|
|
28
29
|
LOGOUT_FAILED: string;
|
|
29
30
|
INVALID_CREDENTIALS: string;
|
|
30
31
|
LOGIN_FAILED: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-messages.d.ts","sourceRoot":"","sources":["../../src/constants/error-messages.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc
|
|
1
|
+
{"version":3,"file":"error-messages.d.ts","sourceRoot":"","sources":["../../src/constants/error-messages.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6BAyCE,MAAM,EAAE;2CACM,MAAM;+BAClB,MAAM;uCACE,MAAM;;;;;;;;;;;;;;;;6BAgChB,MAAM;;;;0BAOT,MAAM,GAAG,MAAM;mCACN,MAAM;;;;;;mCASN,MAAM,GAAG,MAAM;qCACb,MAAM;yDACc,MAAM;qCAC1B,MAAM;;mCAIR,MAAM;8BACX,MAAM,GAAG,MAAM;gCAGb,MAAM,GAAG,MAAM;;;kCAKb,MAAM,GAAG,MAAM;qCACZ,MAAM;8CAGG,MAAM,GAAG,MAAM;;yCAIpB,MAAM;;;;;yBAYtC,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;CAElD,CAAC"}
|
|
@@ -28,6 +28,7 @@ exports.ERROR_MESSAGES = {
|
|
|
28
28
|
ACCESS_DENIED: 'Access denied',
|
|
29
29
|
INVALID_USER_PROFILE: 'Invalid user profile',
|
|
30
30
|
GOOGLE_OAUTH_PROFILE_FETCH_FAILED: 'Failed to fetch user profile from Google OAuth service',
|
|
31
|
+
MICROSOFT_ACTIVE_DIRECTORY_OAUTH_PROFILE_FETCH_FAILED: 'Failed to fetch user profile from Microsoft Active Directory OAuth service',
|
|
31
32
|
LOGOUT_FAILED: 'Logout failed due to an unexpected error.',
|
|
32
33
|
INVALID_CREDENTIALS: 'Invalid credentials',
|
|
33
34
|
LOGIN_FAILED: 'Login Failed',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-messages.js","sourceRoot":"","sources":["../../src/constants/error-messages.ts"],"names":[],"mappings":"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","sourcesContent":["// backend/common/constants/error-messages.ts\n\nexport const ERROR_MESSAGES = {\n //authentication errors\n USER_NOT_FOUND: 'User does not exist.',\n USER_NOT_ACTIVE: 'User profile is not activated.',\n USER_INACTIVE: 'User is inactive.',\n ACCOUNT_BLOCKED: 'Your account has been blocked due to multiple failed login attempts.',\n PASSWORD_INCORRECT: 'Password does not match.',\n PUBLIC_REGISTRATION_DISABLED: 'Public registrations are disabled.',\n UNIQUE_CONSTRAINT_VIOLATION: 'A unique constraint violation occurred.',\n PASSWORDLESS_REGISTRATION_DISABLED: 'Passwordless registration is not enabled.',\n REGISTRATION_REQUIRES_CONTACT: 'Either mobile or email is required for initiating registration.',\n EMAIL_REQUIRED_FOR_VALIDATION: 'Email is required for email validation source.',\n MOBILE_REQUIRED_FOR_VALIDATION: 'Mobile is required for mobile validation source.',\n USER_ALREADY_EXISTS: 'User already exists. Please sign in.',\n VALIDATION_SOURCE_REQUIRED: 'At least one validation source is required.',\n INVALID_OTP: 'Invalid OTP.',\n OTP_EXPIRED: 'OTP has expired.',\n INVALID_VERIFICATION_TYPE: 'Invalid type. Must be either email or mobile.',\n NON_LOCAL_PROVIDER: 'User seems to have used a passwordless mode to authenticate.',\n USER_ID_MISMATCH: \"User ID's do not match.\",\n USERNAME_MISMATCH: \"User username's do not match.\",\n INCORRECT_CURRENT_PASSWORD: 'Incorrect current password specified.',\n PASSWORD_REUSED: 'Try a different password',\n INVALID_VERIFICATION_TOKEN: 'Invalid verification token',\n INVALID_REFRESH_TOKEN: 'Invalid refresh token',\n ACCESS_DENIED: 'Access denied',\n INVALID_USER_PROFILE: 'Invalid user profile',\n GOOGLE_OAUTH_PROFILE_FETCH_FAILED: 'Failed to fetch user profile from Google OAuth service',\n LOGOUT_FAILED: 'Logout failed due to an unexpected error.',\n\n INVALID_CREDENTIALS: 'Invalid credentials',\n LOGIN_FAILED: 'Login Failed',\n OLD_PASSWORD_INCORRECT: 'You have specified an incorrect old password.',\n INVALID_NEW_PASSWORD: 'Invalid new password.',\n PASSWORDS_DO_NOT_MATCH: 'New passwords are not matching.',\n\n // user management errors\n DELETE_SELF_NOT_ALLOWED: 'Deleting logged-in user is not allowed.',\n DELETE_IDS_REQUIRED: 'At least one ID is required for deletion.',\n USER_MISSING_ID: 'User must exist before initializing roles.',\n ROLES_NOT_FOUND: (roles: string[]) => `The following roles were not found: ${roles.join(', ')}`,\n USER_NOT_FOUND_BY_USERNAME: (username: string) => `User with username '${username}' not found.`,\n ROLE_NOT_FOUND: (roleName: string) => `Role '${roleName}' not found.`,\n PERMISSION_NOT_EXIST: (permission: string) => `Permission '${permission}' does not exist.`,\n\n // session errors\n SESSION_INVALID: 'Your session is no longer valid. Please log in again.',\n SESSION_EXPIRED: 'Your session has expired. Please log in again.',\n\n // filter errors\n GROUP_BY_LIMIT: 'buildFilterQuery: Only 1 Group by field is supported currently.',\n INVALID_GROUP_BY_COUNT: 'Exactly one groupBy field is required to count grouped records.',\n\n // general errors\n FORBIDDEN: 'Forbidden',\n\n\n // database errors\n DUPLICATE_ENTRY: 'Duplicate entry. A record with similar unique fields already exists.',\n\n // validation errors\n ID_REQUIRED_FOR_UPDATE: 'Id is required for update.',\n ID_REQUIRED_FOR_DELETE: 'Id is required for deletion.',\n\n // CRUD service errors\n RELATION_TYPE_NOT_SUPPORTED: 'Relation type not supported in CRUD service.',\n NO_SOFT_DELETED_RECORD_FOUND: 'No soft-deleted record found with the given ID.',\n CONFLICTING_RECORD_ON_UNARCHIVE: 'Another record is conflicting with the record you are attempting to Un-Archive, either delete or change the other record so as to avoid this conflict.',\n NO_SOFT_DELETED_RECORDS_FOUND: 'No matching soft-deleted records found.',\n EMPTY_PATH_PARTS: 'Path parts cannot be empty',\n\n\n // CSV/Excel service errors\n MISSING_HEADERS_OR_FUNCTION: 'Either headers or data records function must be provided.',\n INVALID_CHUNK_SIZE: 'Chunk size must be greater than 0 when data records function is provided.',\n INVALID_FORMAT: (format: string) => `Invalid ${format} format`,\n\n\n //field errors\n INVALID_INVERSE_FIELD_TYPE: 'Only relation fields can have inverse fields.',\n MODEL_AND_MODULE_REQUIRED_TO_UPDATE_INVERSE_FIELD: 'Model and module are required to update inverse field.',\n MODEL_NAME_AND_MODULE_NAME_REQUIRED_TO_CREATE_INVERSE_FIELD: 'Model name and module name are required to create inverse field.',\n FIELD_NOT_FOUND: (id: number | string) => `No field with id #${id} exists`,\n PROVIDER_NOT_FOUND: (provider: string) => `Field incorrectly configured. No provider with name ${provider} registered in backend.`,\n FILE_WRITE_FAILED: 'File creation failed, rolling back transaction',\n FILE_DELETE_FAILED: 'File deletion failed, rolling back transaction',\n // file service errors\n FILE_NOT_FOUND: 'File not found',\n FILE_COPY_ERROR: 'Error copying file',\n S3_CLIENT_NOT_INITIALIZED: 'S3 Client not initialized. Please check the S3 configuration.',\n\n // model errors\n MODEL_METADATA_NOT_FOUND: (id: string | number) => `Model metadata with ID ${id} not found.`,\n MODEL_SERVICE_NOT_FOUND: (model: string) => `Model service for ${model} not found.`,\n RELATION_CO_MODEL_NOT_DEFINED_FOR_FIELD: (fieldName: string) => `Relation coModelSingularName is not defined for relation field ${fieldName}`,\n MODEL_NOT_FOUND: (singularName?: string) => `Model${singularName ? ` with singular name \"${singularName}\"` : ''} not found.`,\n MODEL_REQUIRED_FOR_CODE_GENERATION: 'Model ID or Model Name is required for generating code.',\n\n //module errors\n MODULE_NOT_FOUND: (moduleName: string) => `Module with name ${moduleName} not found.`,\n MODULE_ID_NOT_FOUND: (id: string | number) => `Module with ID ${id} not found.`,\n\n //entity errors\n ENTITY_NOT_FOUND: (entity?: string | number) => `Entity${entity ? ' ' + entity : ''} not found.`,\n ENTITY_NAME_REQUIRED: 'Entity name is required to find the entity.',\n ENTITY_ID_REQUIRED: 'Entity ID is required to find the entity.',\n\n // import errors\n NO_ERROR_LOG_FOR_IMPORT: (id: string | number) => `No error log entries found for import transaction ID ${id}.`,\n FILE_READ_FAILED_FROM_URL: (url: string) => `Failed to read file from URL: ${url}`,\n\n // media storage provider errors\n MEDIA_STORAGE_PROVIDER_ID_NOT_FOUND: (id: number | string) => `Media Storage Provider with #${id} not found.`,\n MEDIA_STORAGE_PROVIDER_NOT_FOUND: 'Media Storage Provider not found',\n\n // SQL errors\n UNSUPPORTED_SQL_OPERATOR: (operator: string) => `Unsupported SQL operator: ${operator}`,\n\n // AI interaction errors\n PYTHON_EXECUTABLE_NOT_CONFIGURED: 'SolidX AI MCP python executable or client path not configured.',\n UNABLE_TO_RESOLVE_SOLID_COMMAND: 'Unable to resolve a solid_ command that was used to come up with this response.',\n UNABLE_TO_RESOLVE_MCP_HANDLER: 'Unable to resolve a mcp tool handler.',\n\n DEFAULT_REGEX_PATTERN_NOT_MATCHING_ERROR_MSG: 'Invalid regex pattern.',\n\n PERMISSION_MESSAGES: {\n insertMany: (model?: string) =>\n `You do not have permission to import ${model ?? 'these'} records.`,\n } as Record<string, (model?: string) => string>,\n\n};"]}
|
|
1
|
+
{"version":3,"file":"error-messages.js","sourceRoot":"","sources":["../../src/constants/error-messages.ts"],"names":[],"mappings":"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","sourcesContent":["// backend/common/constants/error-messages.ts\n\nexport const ERROR_MESSAGES = {\n //authentication errors\n USER_NOT_FOUND: 'User does not exist.',\n USER_NOT_ACTIVE: 'User profile is not activated.',\n USER_INACTIVE: 'User is inactive.',\n ACCOUNT_BLOCKED: 'Your account has been blocked due to multiple failed login attempts.',\n PASSWORD_INCORRECT: 'Password does not match.',\n PUBLIC_REGISTRATION_DISABLED: 'Public registrations are disabled.',\n UNIQUE_CONSTRAINT_VIOLATION: 'A unique constraint violation occurred.',\n PASSWORDLESS_REGISTRATION_DISABLED: 'Passwordless registration is not enabled.',\n REGISTRATION_REQUIRES_CONTACT: 'Either mobile or email is required for initiating registration.',\n EMAIL_REQUIRED_FOR_VALIDATION: 'Email is required for email validation source.',\n MOBILE_REQUIRED_FOR_VALIDATION: 'Mobile is required for mobile validation source.',\n USER_ALREADY_EXISTS: 'User already exists. Please sign in.',\n VALIDATION_SOURCE_REQUIRED: 'At least one validation source is required.',\n INVALID_OTP: 'Invalid OTP.',\n OTP_EXPIRED: 'OTP has expired.',\n INVALID_VERIFICATION_TYPE: 'Invalid type. Must be either email or mobile.',\n NON_LOCAL_PROVIDER: 'User seems to have used a passwordless mode to authenticate.',\n USER_ID_MISMATCH: \"User ID's do not match.\",\n USERNAME_MISMATCH: \"User username's do not match.\",\n INCORRECT_CURRENT_PASSWORD: 'Incorrect current password specified.',\n PASSWORD_REUSED: 'Try a different password',\n INVALID_VERIFICATION_TOKEN: 'Invalid verification token',\n INVALID_REFRESH_TOKEN: 'Invalid refresh token',\n ACCESS_DENIED: 'Access denied',\n INVALID_USER_PROFILE: 'Invalid user profile',\n GOOGLE_OAUTH_PROFILE_FETCH_FAILED: 'Failed to fetch user profile from Google OAuth service',\n MICROSOFT_ACTIVE_DIRECTORY_OAUTH_PROFILE_FETCH_FAILED: 'Failed to fetch user profile from Microsoft Active Directory OAuth service',\n LOGOUT_FAILED: 'Logout failed due to an unexpected error.',\n\n INVALID_CREDENTIALS: 'Invalid credentials',\n LOGIN_FAILED: 'Login Failed',\n OLD_PASSWORD_INCORRECT: 'You have specified an incorrect old password.',\n INVALID_NEW_PASSWORD: 'Invalid new password.',\n PASSWORDS_DO_NOT_MATCH: 'New passwords are not matching.',\n\n // user management errors\n DELETE_SELF_NOT_ALLOWED: 'Deleting logged-in user is not allowed.',\n DELETE_IDS_REQUIRED: 'At least one ID is required for deletion.',\n USER_MISSING_ID: 'User must exist before initializing roles.',\n ROLES_NOT_FOUND: (roles: string[]) => `The following roles were not found: ${roles.join(', ')}`,\n USER_NOT_FOUND_BY_USERNAME: (username: string) => `User with username '${username}' not found.`,\n ROLE_NOT_FOUND: (roleName: string) => `Role '${roleName}' not found.`,\n PERMISSION_NOT_EXIST: (permission: string) => `Permission '${permission}' does not exist.`,\n\n // session errors\n SESSION_INVALID: 'Your session is no longer valid. Please log in again.',\n SESSION_EXPIRED: 'Your session has expired. Please log in again.',\n\n // filter errors\n GROUP_BY_LIMIT: 'buildFilterQuery: Only 1 Group by field is supported currently.',\n INVALID_GROUP_BY_COUNT: 'Exactly one groupBy field is required to count grouped records.',\n\n // general errors\n FORBIDDEN: 'Forbidden',\n\n\n // database errors\n DUPLICATE_ENTRY: 'Duplicate entry. A record with similar unique fields already exists.',\n\n // validation errors\n ID_REQUIRED_FOR_UPDATE: 'Id is required for update.',\n ID_REQUIRED_FOR_DELETE: 'Id is required for deletion.',\n\n // CRUD service errors\n RELATION_TYPE_NOT_SUPPORTED: 'Relation type not supported in CRUD service.',\n NO_SOFT_DELETED_RECORD_FOUND: 'No soft-deleted record found with the given ID.',\n CONFLICTING_RECORD_ON_UNARCHIVE: 'Another record is conflicting with the record you are attempting to Un-Archive, either delete or change the other record so as to avoid this conflict.',\n NO_SOFT_DELETED_RECORDS_FOUND: 'No matching soft-deleted records found.',\n EMPTY_PATH_PARTS: 'Path parts cannot be empty',\n\n\n // CSV/Excel service errors\n MISSING_HEADERS_OR_FUNCTION: 'Either headers or data records function must be provided.',\n INVALID_CHUNK_SIZE: 'Chunk size must be greater than 0 when data records function is provided.',\n INVALID_FORMAT: (format: string) => `Invalid ${format} format`,\n\n\n //field errors\n INVALID_INVERSE_FIELD_TYPE: 'Only relation fields can have inverse fields.',\n MODEL_AND_MODULE_REQUIRED_TO_UPDATE_INVERSE_FIELD: 'Model and module are required to update inverse field.',\n MODEL_NAME_AND_MODULE_NAME_REQUIRED_TO_CREATE_INVERSE_FIELD: 'Model name and module name are required to create inverse field.',\n FIELD_NOT_FOUND: (id: number | string) => `No field with id #${id} exists`,\n PROVIDER_NOT_FOUND: (provider: string) => `Field incorrectly configured. No provider with name ${provider} registered in backend.`,\n FILE_WRITE_FAILED: 'File creation failed, rolling back transaction',\n FILE_DELETE_FAILED: 'File deletion failed, rolling back transaction',\n // file service errors\n FILE_NOT_FOUND: 'File not found',\n FILE_COPY_ERROR: 'Error copying file',\n S3_CLIENT_NOT_INITIALIZED: 'S3 Client not initialized. Please check the S3 configuration.',\n\n // model errors\n MODEL_METADATA_NOT_FOUND: (id: string | number) => `Model metadata with ID ${id} not found.`,\n MODEL_SERVICE_NOT_FOUND: (model: string) => `Model service for ${model} not found.`,\n RELATION_CO_MODEL_NOT_DEFINED_FOR_FIELD: (fieldName: string) => `Relation coModelSingularName is not defined for relation field ${fieldName}`,\n MODEL_NOT_FOUND: (singularName?: string) => `Model${singularName ? ` with singular name \"${singularName}\"` : ''} not found.`,\n MODEL_REQUIRED_FOR_CODE_GENERATION: 'Model ID or Model Name is required for generating code.',\n\n //module errors\n MODULE_NOT_FOUND: (moduleName: string) => `Module with name ${moduleName} not found.`,\n MODULE_ID_NOT_FOUND: (id: string | number) => `Module with ID ${id} not found.`,\n\n //entity errors\n ENTITY_NOT_FOUND: (entity?: string | number) => `Entity${entity ? ' ' + entity : ''} not found.`,\n ENTITY_NAME_REQUIRED: 'Entity name is required to find the entity.',\n ENTITY_ID_REQUIRED: 'Entity ID is required to find the entity.',\n\n // import errors\n NO_ERROR_LOG_FOR_IMPORT: (id: string | number) => `No error log entries found for import transaction ID ${id}.`,\n FILE_READ_FAILED_FROM_URL: (url: string) => `Failed to read file from URL: ${url}`,\n\n // media storage provider errors\n MEDIA_STORAGE_PROVIDER_ID_NOT_FOUND: (id: number | string) => `Media Storage Provider with #${id} not found.`,\n MEDIA_STORAGE_PROVIDER_NOT_FOUND: 'Media Storage Provider not found',\n\n // SQL errors\n UNSUPPORTED_SQL_OPERATOR: (operator: string) => `Unsupported SQL operator: ${operator}`,\n\n // AI interaction errors\n PYTHON_EXECUTABLE_NOT_CONFIGURED: 'SolidX AI MCP python executable or client path not configured.',\n UNABLE_TO_RESOLVE_SOLID_COMMAND: 'Unable to resolve a solid_ command that was used to come up with this response.',\n UNABLE_TO_RESOLVE_MCP_HANDLER: 'Unable to resolve a mcp tool handler.',\n\n DEFAULT_REGEX_PATTERN_NOT_MATCHING_ERROR_MSG: 'Invalid regex pattern.',\n\n PERMISSION_MESSAGES: {\n insertMany: (model?: string) =>\n `You do not have permission to import ${model ?? 'these'} records.`,\n } as Record<string, (model?: string) => string>,\n\n};\n"]}
|