@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.
Files changed (55) hide show
  1. package/MICROSOFT_ACTIVE_DIRECTORY_OAUTH_FLOW.md +616 -0
  2. package/dist/constants/error-messages.d.ts +1 -0
  3. package/dist/constants/error-messages.d.ts.map +1 -1
  4. package/dist/constants/error-messages.js +1 -0
  5. package/dist/constants/error-messages.js.map +1 -1
  6. package/dist/controllers/microsoft-active-directory-authentication.controller.d.ts +28 -0
  7. package/dist/controllers/microsoft-active-directory-authentication.controller.d.ts.map +1 -0
  8. package/dist/controllers/microsoft-active-directory-authentication.controller.js +122 -0
  9. package/dist/controllers/microsoft-active-directory-authentication.controller.js.map +1 -0
  10. package/dist/entities/user.entity.d.ts +3 -0
  11. package/dist/entities/user.entity.d.ts.map +1 -1
  12. package/dist/entities/user.entity.js +13 -1
  13. package/dist/entities/user.entity.js.map +1 -1
  14. package/dist/helpers/microsoft-active-directory-oauth.helper.d.ts +34 -0
  15. package/dist/helpers/microsoft-active-directory-oauth.helper.d.ts.map +1 -0
  16. package/dist/helpers/microsoft-active-directory-oauth.helper.js +43 -0
  17. package/dist/helpers/microsoft-active-directory-oauth.helper.js.map +1 -0
  18. package/dist/helpers/user-helper.d.ts.map +1 -1
  19. package/dist/helpers/user-helper.js +4 -0
  20. package/dist/helpers/user-helper.js.map +1 -1
  21. package/dist/index.d.ts +1 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +1 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/passport-strategies/microsoft-active-directory-oauth.strategy.d.ts +14 -0
  26. package/dist/passport-strategies/microsoft-active-directory-oauth.strategy.d.ts.map +1 -0
  27. package/dist/passport-strategies/microsoft-active-directory-oauth.strategy.js +67 -0
  28. package/dist/passport-strategies/microsoft-active-directory-oauth.strategy.js.map +1 -0
  29. package/dist/services/authentication.service.d.ts +12 -0
  30. package/dist/services/authentication.service.d.ts.map +1 -1
  31. package/dist/services/authentication.service.js +64 -0
  32. package/dist/services/authentication.service.js.map +1 -1
  33. package/dist/services/settings/default-settings-provider.service.d.ts +100 -0
  34. package/dist/services/settings/default-settings-provider.service.d.ts.map +1 -1
  35. package/dist/services/settings/default-settings-provider.service.js +56 -0
  36. package/dist/services/settings/default-settings-provider.service.js.map +1 -1
  37. package/dist/services/user.service.d.ts +2 -0
  38. package/dist/services/user.service.d.ts.map +1 -1
  39. package/dist/services/user.service.js +46 -0
  40. package/dist/services/user.service.js.map +1 -1
  41. package/dist/solid-core.module.d.ts.map +1 -1
  42. package/dist/solid-core.module.js +4 -0
  43. package/dist/solid-core.module.js.map +1 -1
  44. package/package.json +1 -1
  45. package/src/constants/error-messages.ts +2 -1
  46. package/src/controllers/microsoft-active-directory-authentication.controller.ts +104 -0
  47. package/src/entities/user.entity.ts +13 -1
  48. package/src/helpers/microsoft-active-directory-oauth.helper.ts +83 -0
  49. package/src/helpers/user-helper.ts +5 -1
  50. package/src/index.ts +1 -0
  51. package/src/passport-strategies/microsoft-active-directory-oauth.strategy.ts +63 -0
  52. package/src/services/authentication.service.ts +78 -0
  53. package/src/services/settings/default-settings-provider.service.ts +59 -0
  54. package/src/services/user.service.ts +61 -0
  55. package/src/solid-core.module.ts +4 -0
@@ -0,0 +1,104 @@
1
+ import {
2
+ Controller,
3
+ Get,
4
+ InternalServerErrorException,
5
+ Query,
6
+ Req,
7
+ Res,
8
+ UseGuards,
9
+ } from "@nestjs/common";
10
+ import { ApiQuery, ApiTags } from "@nestjs/swagger";
11
+ import { Request, Response } from "express";
12
+ import {
13
+ DEFAULT_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_TENANT,
14
+ MicrosoftActiveDirectoryAuthConfiguration,
15
+ isMicrosoftActiveDirectoryOAuthConfigured,
16
+ } from "../helpers/microsoft-active-directory-oauth.helper";
17
+ import { AuthenticationService } from "../services/authentication.service";
18
+ import { SettingService } from "../services/setting.service";
19
+ import { Public } from "src/decorators/public.decorator";
20
+ import { Auth } from "../decorators/auth.decorator";
21
+ import { AuthType } from "../enums/auth-type.enum";
22
+ import { MicrosoftActiveDirectoryOauthGuard } from "../passport-strategies/microsoft-active-directory-oauth.strategy";
23
+ import { UserService } from "../services/user.service";
24
+ import type { SolidCoreSetting } from "../services/settings/default-settings-provider.service";
25
+
26
+ @Auth(AuthType.None)
27
+ @ApiTags("Solid Core")
28
+ @Controller("iam/microsoft-active-directory")
29
+ export class MicrosoftActiveDirectoryAuthenticationController {
30
+ constructor(
31
+ private readonly userService: UserService,
32
+ private readonly authService: AuthenticationService,
33
+ private readonly settingService: SettingService,
34
+ ) {}
35
+
36
+ private async getConfiguration(): Promise<MicrosoftActiveDirectoryAuthConfiguration> {
37
+ return {
38
+ clientID: this.settingService.getConfigValue<SolidCoreSetting>("MICROSOFT_ACTIVE_DIRECTORY_CLIENT_ID"),
39
+ clientSecret: this.settingService.getConfigValue<SolidCoreSetting>("MICROSOFT_ACTIVE_DIRECTORY_CLIENT_SECRET"),
40
+ tenant: this.settingService.getConfigValue<SolidCoreSetting>("MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID" ) ?? DEFAULT_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_TENANT,
41
+ callbackURL: this.settingService.getConfigValue<SolidCoreSetting>("MICROSOFT_ACTIVE_DIRECTORY_CALLBACK_URL"),
42
+ redirectURL: this.settingService.getConfigValue<SolidCoreSetting>("MICROSOFT_ACTIVE_DIRECTORY_REDIRECT_URL"),
43
+ };
44
+ }
45
+
46
+ private buildFrontendRedirectUrl(redirectURL: string, accessCode: string) {
47
+ const separator = redirectURL.includes("?") ? "&" : "?";
48
+ return `${redirectURL}${separator}accessCode=${encodeURIComponent(accessCode)}`;
49
+ }
50
+
51
+ private async validateConfiguration() {
52
+ const config = await this.getConfiguration();
53
+ if (!isMicrosoftActiveDirectoryOAuthConfigured(config)) {
54
+ throw new InternalServerErrorException("Microsoft Active Directory OAuth is not configured");
55
+ }
56
+ return config;
57
+ }
58
+
59
+ @Public()
60
+ @UseGuards(MicrosoftActiveDirectoryOauthGuard)
61
+ @Get("connect")
62
+ async connect() {
63
+ await this.validateConfiguration();
64
+ }
65
+
66
+ @Public()
67
+ @Get("connect/callback")
68
+ @UseGuards(MicrosoftActiveDirectoryOauthGuard)
69
+ async microsoftActiveDirectoryAuthCallback(
70
+ @Req() req: Request,
71
+ @Res() res: Response,
72
+ ) {
73
+ const config = await this.validateConfiguration();
74
+ const user = req.user;
75
+ return res.redirect(
76
+ this.buildFrontendRedirectUrl(config.redirectURL, user["accessCode"]),
77
+ );
78
+ }
79
+
80
+ @Public()
81
+ @Get("dummy-redirect")
82
+ async dummyMicrosoftActiveDirectoryAuthRedirect(
83
+ @Query("accessCode") accessCode,
84
+ ) {
85
+ await this.validateConfiguration();
86
+ const user = await this.userService.findOneByAccessCode(accessCode);
87
+
88
+ if (user) {
89
+ delete user["password"];
90
+ }
91
+
92
+ return user;
93
+ }
94
+
95
+ @Public()
96
+ @Get("authenticate")
97
+ @ApiQuery({ name: "accessCode", required: true, type: String })
98
+ async microsoftActiveDirectoryAuth(
99
+ @Query("accessCode") accessCode: string,
100
+ ) {
101
+ await this.validateConfiguration();
102
+ return this.authService.signInUsingMicrosoftActiveDirectory(accessCode);
103
+ }
104
+ }
@@ -89,6 +89,18 @@ export class User extends CommonEntity {
89
89
  // don't send to client
90
90
  microsoftProfilePicture: string;
91
91
 
92
+ @Column({ type: "varchar", nullable: true })
93
+ // don't send to client
94
+ microsoftActiveDirectoryId: string;
95
+
96
+ @Column({ type: "varchar", nullable: true })
97
+ // don't send to client
98
+ microsoftActiveDirectoryAccessToken: string;
99
+
100
+ @Column({ type: "varchar", nullable: true })
101
+ // don't send to client
102
+ microsoftActiveDirectoryProfilePicture: string;
103
+
92
104
  @Index()
93
105
  @Column({ default: true })
94
106
  @Expose()
@@ -194,4 +206,4 @@ export class User extends CommonEntity {
194
206
  @Expose()
195
207
  apiKeys: UserApiKey[];
196
208
 
197
- }
209
+ }
@@ -0,0 +1,83 @@
1
+ export type MicrosoftActiveDirectoryAuthConfiguration = {
2
+ clientID: string;
3
+ clientSecret: string;
4
+ tenant: string;
5
+ callbackURL: string;
6
+ redirectURL: string;
7
+ };
8
+
9
+ export const DEFAULT_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_TENANT = "common";
10
+ export const MICROSOFT_ACTIVE_DIRECTORY_OAUTH_SCOPES = [
11
+ "openid",
12
+ "profile",
13
+ "email",
14
+ "User.Read",
15
+ ];
16
+
17
+ type MicrosoftActiveDirectoryOauthProfileValue = {
18
+ value?: string;
19
+ };
20
+
21
+ export type MicrosoftActiveDirectoryOauthProfile = {
22
+ id?: string;
23
+ displayName?: string;
24
+ emails?: MicrosoftActiveDirectoryOauthProfileValue[];
25
+ photos?: MicrosoftActiveDirectoryOauthProfileValue[];
26
+ _json?: {
27
+ id?: string;
28
+ displayName?: string;
29
+ mail?: string;
30
+ userPrincipalName?: string;
31
+ email?: string;
32
+ preferred_username?: string;
33
+ picture?: string;
34
+ };
35
+ };
36
+
37
+ export const isMicrosoftActiveDirectoryOAuthConfigured = (
38
+ config: MicrosoftActiveDirectoryAuthConfiguration,
39
+ ): boolean => {
40
+ return !!(
41
+ config.clientID &&
42
+ config.clientSecret &&
43
+ config.tenant &&
44
+ config.callbackURL &&
45
+ config.redirectURL
46
+ );
47
+ };
48
+
49
+ export const getMicrosoftActiveDirectoryOAuthProfileId = (
50
+ profile: MicrosoftActiveDirectoryOauthProfile,
51
+ ): string | null => {
52
+ return profile.id || profile._json?.id || null;
53
+ };
54
+
55
+ export const getMicrosoftActiveDirectoryOAuthEmail = (
56
+ profile: MicrosoftActiveDirectoryOauthProfile,
57
+ ): string | null => {
58
+ const email =
59
+ profile.emails?.find((item) => !!item.value)?.value ||
60
+ profile._json?.mail ||
61
+ profile._json?.userPrincipalName ||
62
+ profile._json?.email ||
63
+ profile._json?.preferred_username ||
64
+ null;
65
+
66
+ return email?.trim().toLowerCase() || null;
67
+ };
68
+
69
+ export const getMicrosoftActiveDirectoryOAuthDisplayName = (
70
+ profile: MicrosoftActiveDirectoryOauthProfile,
71
+ ): string | null => {
72
+ return profile.displayName || profile._json?.displayName || null;
73
+ };
74
+
75
+ export const getMicrosoftActiveDirectoryOAuthPicture = (
76
+ profile: MicrosoftActiveDirectoryOauthProfile,
77
+ ): string | null => {
78
+ return (
79
+ profile.photos?.find((item) => !!item.value)?.value ||
80
+ profile._json?.picture ||
81
+ null
82
+ );
83
+ };
@@ -15,6 +15,10 @@ export function getUserExcludedFields(): string[] {
15
15
  "facebookId",
16
16
  "microsoftAccessToken",
17
17
  "microsoftId",
18
+ "microsoftProfilePicture",
19
+ "microsoftActiveDirectoryAccessToken",
20
+ "microsoftActiveDirectoryId",
21
+ "microsoftActiveDirectoryProfilePicture",
18
22
  "forgotPasswordConfirmedAt",
19
23
  "verificationTokenOnForgotPassword",
20
24
  "verificationTokenOnForgotPasswordExpiresAt",
@@ -38,4 +42,4 @@ export function getUserExcludedFields(): string[] {
38
42
  "userViewMetadataIds",
39
43
  "userViewMetadataCommand",
40
44
  ];
41
- }
45
+ }
package/src/index.ts CHANGED
@@ -269,6 +269,7 @@ export * from './listeners/user-registration.listener'
269
269
  export * from './passport-strategies/google-oauth.strategy'
270
270
  export * from './passport-strategies/facebook-oauth.strategy'
271
271
  export * from './passport-strategies/microsoft-oauth.strategy'
272
+ export * from './passport-strategies/microsoft-active-directory-oauth.strategy'
272
273
 
273
274
  export * from './services/selection-providers/list-of-values-selection-providers.service'
274
275
 
@@ -0,0 +1,63 @@
1
+ import { Injectable, Logger, UnauthorizedException } from '@nestjs/common';
2
+ import { AuthGuard, PassportStrategy } from '@nestjs/passport';
3
+ import { Strategy } from 'passport-microsoft';
4
+ import { DEFAULT_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_TENANT, MICROSOFT_ACTIVE_DIRECTORY_OAUTH_SCOPES, MicrosoftActiveDirectoryAuthConfiguration, getMicrosoftActiveDirectoryOAuthDisplayName, getMicrosoftActiveDirectoryOAuthEmail, getMicrosoftActiveDirectoryOAuthPicture, getMicrosoftActiveDirectoryOAuthProfileId, isMicrosoftActiveDirectoryOAuthConfigured } from 'src/helpers/microsoft-active-directory-oauth.helper';
5
+ import { v4 as uuid } from 'uuid';
6
+ import { UserService } from '../services/user.service';
7
+
8
+ const DUMMY_CLIENT_ID = 'DUMMY_CLIENT_ID';
9
+ const DUMMY_CLIENT_SECRET = 'DUMMY_CLIENT_SECRET';
10
+ const DUMMY_CALLBACK_URL = 'DUMMY_CALLBACK_URL';
11
+
12
+ @Injectable()
13
+ export class MicrosoftActiveDirectoryOauthGuard extends AuthGuard('microsoft-active-directory') { }
14
+
15
+
16
+ @Injectable()
17
+ export class MicrosoftActiveDirectoryOAuthStrategy extends PassportStrategy(Strategy, 'microsoft-active-directory') {
18
+ private readonly logger = new Logger(MicrosoftActiveDirectoryOAuthStrategy.name);
19
+ constructor(private readonly userService: UserService) {
20
+ // TODO: Have added default dummy values for the configuration, since the configuration is not mandatory.
21
+ // Perhaps a cleaner way needs to be figured out
22
+ const clientID = process.env.IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_CLIENT_ID || DUMMY_CLIENT_ID;
23
+ const clientSecret = process.env.IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_CLIENT_SECRET || DUMMY_CLIENT_SECRET;
24
+ const tenant = process.env.IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_TENANT_ID || DEFAULT_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_TENANT;
25
+ const callbackURL = process.env.IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_CALLBACK_URL || DUMMY_CALLBACK_URL;
26
+ const redirectURL = process.env.IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_REDIRECT_URL;
27
+
28
+ super({ clientID, clientSecret, callbackURL, tenant, scope: MICROSOFT_ACTIVE_DIRECTORY_OAUTH_SCOPES, addUPNAsEmail: true });
29
+
30
+ const microsoftActiveDirectoryOauth: MicrosoftActiveDirectoryAuthConfiguration = { clientID, clientSecret, tenant, callbackURL, redirectURL }
31
+ if (!isMicrosoftActiveDirectoryOAuthConfigured(microsoftActiveDirectoryOauth)) {
32
+ this.logger.debug('Microsoft Active Directory OAuth strategy is not configured');
33
+ }
34
+ }
35
+
36
+ async validate(_accessToken: string, _refreshToken: string, profile: any, done: any): Promise<any> {
37
+ const providerId = getMicrosoftActiveDirectoryOAuthProfileId(profile);
38
+
39
+ if (!providerId) {
40
+ return done(new UnauthorizedException('Microsoft Active Directory OAuth profile is missing an id'), false);
41
+ }
42
+
43
+ // generate a unique access code.
44
+ const loginAccessCode: string = uuid();
45
+
46
+ const user = {
47
+ provider: 'microsoftActiveDirectory',
48
+ providerId,
49
+ email: getMicrosoftActiveDirectoryOAuthEmail(profile),
50
+ name: getMicrosoftActiveDirectoryOAuthDisplayName(profile) || getMicrosoftActiveDirectoryOAuthEmail(profile) || 'Microsoft Active Directory User',
51
+ picture: getMicrosoftActiveDirectoryOAuthPicture(profile),
52
+ accessCode: loginAccessCode,
53
+ };
54
+
55
+ // store the access code and the access token in the database.
56
+ // while doing this we also check if the user exists in the database if not we create one.
57
+ // if exists then we update the user and store the specified access code & token.
58
+ await this.userService.resolveUserOnOauthMicrosoftActiveDirectory({ ...user, accessToken: _accessToken, refreshToken: null });
59
+
60
+ done(null, user);
61
+ }
62
+
63
+ }
@@ -1997,6 +1997,84 @@ export class AuthenticationService {
1997
1997
  };
1998
1998
  }
1999
1999
 
2000
+ async validateUserUsingMicrosoftActiveDirectory(user: User) {
2001
+ if (
2002
+ !user.microsoftActiveDirectoryAccessToken ||
2003
+ !user.microsoftActiveDirectoryId
2004
+ ) {
2005
+ throw new UnauthorizedException(ERROR_MESSAGES.USER_NOT_FOUND);
2006
+ }
2007
+
2008
+ try {
2009
+ const response = await this.httpService.axiosRef.get(
2010
+ `https://graph.microsoft.com/v1.0/me?$select=id,displayName,mail,userPrincipalName`,
2011
+ {
2012
+ headers: {
2013
+ Authorization: `Bearer ${user.microsoftActiveDirectoryAccessToken}`,
2014
+ },
2015
+ },
2016
+ );
2017
+ const userProfile = response.data;
2018
+ const profileEmail = (userProfile.mail || userProfile.userPrincipalName)
2019
+ ?.trim()
2020
+ .toLowerCase();
2021
+ const userEmail = user.email?.trim().toLowerCase();
2022
+
2023
+ if (
2024
+ userProfile.id === user.microsoftActiveDirectoryId &&
2025
+ (!userEmail || profileEmail === userEmail)
2026
+ ) {
2027
+ return userProfile;
2028
+ } else {
2029
+ throw new UnauthorizedException(ERROR_MESSAGES.INVALID_USER_PROFILE);
2030
+ }
2031
+ } catch (error: any) {
2032
+ if (error instanceof UnauthorizedException) {
2033
+ throw error;
2034
+ }
2035
+
2036
+ throw new UnauthorizedException(
2037
+ ERROR_MESSAGES.MICROSOFT_ACTIVE_DIRECTORY_OAUTH_PROFILE_FETCH_FAILED,
2038
+ );
2039
+ }
2040
+ }
2041
+
2042
+ async signInUsingMicrosoftActiveDirectory(accessCode: string) {
2043
+ const user = await this.userRepository.findOne({
2044
+ where: {
2045
+ accessCode: accessCode,
2046
+ },
2047
+ relations: {
2048
+ roles: true,
2049
+ },
2050
+ });
2051
+
2052
+ if (!user) {
2053
+ throw new UnauthorizedException(ERROR_MESSAGES.USER_NOT_FOUND);
2054
+ }
2055
+ this.checkAccountBlocked(user);
2056
+
2057
+ try {
2058
+ await this.validateUserUsingMicrosoftActiveDirectory(user);
2059
+ } catch (e) {
2060
+ await this.incrementFailedAttempts(user);
2061
+ throw e;
2062
+ }
2063
+
2064
+ await this.resetFailedAttempts(user);
2065
+ const tokens = await this.generateTokens(user);
2066
+ return {
2067
+ user: {
2068
+ email: user.email,
2069
+ mobile: user.mobile,
2070
+ username: user.username,
2071
+ id: user.id,
2072
+ roles: user.roles.map((role) => role.name),
2073
+ },
2074
+ ...tokens,
2075
+ };
2076
+ }
2077
+
2000
2078
  async signInUsingApple(accessCode: string) {
2001
2079
  const user = await this.userRepository.findOne({
2002
2080
  where: {
@@ -43,6 +43,16 @@ const getSolidCoreSettings = (isProd: boolean) =>
43
43
  sortOrder: 50,
44
44
  controlType: "boolean",
45
45
  },
46
+ {
47
+ moduleName: "solid-core",
48
+ key: "iamMicrosoftActiveDirectoryOAuthEnabled",
49
+ value: false,
50
+ level: SettingLevel.SystemAdminEditable,
51
+ label: "Allow Login / Signup With Microsoft Active Directory",
52
+ group: "authentication-settings",
53
+ sortOrder: 50,
54
+ controlType: "boolean",
55
+ },
46
56
  {
47
57
  moduleName: "solid-core",
48
58
  key: "authPagesLayout",
@@ -703,6 +713,55 @@ const getSolidCoreSettings = (isProd: boolean) =>
703
713
  level: SettingLevel.SystemAdminReadonly,
704
714
  },
705
715
 
716
+ // microsoft-active-directory-oauth-settings-provider.service.ts
717
+ {
718
+ moduleName: "solid-core",
719
+ key: "MICROSOFT_ACTIVE_DIRECTORY_CLIENT_ID",
720
+ value: process.env.IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_CLIENT_ID,
721
+ level: SettingLevel.SystemAdminReadonly,
722
+ label: "Microsoft Active Directory OAuth Client ID",
723
+ group: "oauth-settings",
724
+ sortOrder: 70,
725
+ controlType: "shortText",
726
+ },
727
+ {
728
+ moduleName: "solid-core",
729
+ key: "MICROSOFT_ACTIVE_DIRECTORY_CLIENT_SECRET",
730
+ value: process.env.IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_CLIENT_SECRET,
731
+ level: SettingLevel.SystemEnv,
732
+ },
733
+ {
734
+ moduleName: "solid-core",
735
+ key: "MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID",
736
+ value:
737
+ process.env.IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_TENANT_ID || "common",
738
+ level: SettingLevel.SystemAdminReadonly,
739
+ label: "Microsoft Active Directory OAuth Tenant ID",
740
+ group: "oauth-settings",
741
+ sortOrder: 80,
742
+ controlType: "shortText",
743
+ },
744
+ {
745
+ moduleName: "solid-core",
746
+ key: "MICROSOFT_ACTIVE_DIRECTORY_CALLBACK_URL",
747
+ value: process.env.IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_CALLBACK_URL,
748
+ level: SettingLevel.SystemAdminReadonly,
749
+ label: "Microsoft Active Directory OAuth Callback URL",
750
+ group: "oauth-settings",
751
+ sortOrder: 90,
752
+ controlType: "shortText",
753
+ },
754
+ {
755
+ moduleName: "solid-core",
756
+ key: "MICROSOFT_ACTIVE_DIRECTORY_REDIRECT_URL",
757
+ value: process.env.IAM_MICROSOFT_ACTIVE_DIRECTORY_OAUTH_REDIRECT_URL,
758
+ level: SettingLevel.SystemAdminReadonly,
759
+ label: "Microsoft Active Directory OAuth Redirect URL",
760
+ group: "oauth-settings",
761
+ sortOrder: 100,
762
+ controlType: "shortText",
763
+ },
764
+
706
765
  // iam-settings-provider.service.ts
707
766
  {
708
767
  moduleName: "solid-core",
@@ -30,6 +30,23 @@ export class UserService extends CRUDService<User> {
30
30
  return normalized || "facebook_user";
31
31
  }
32
32
 
33
+ private buildMicrosoftActiveDirectoryUsernameBase(
34
+ email?: string,
35
+ providerId?: string,
36
+ name?: string,
37
+ ): string {
38
+ const source =
39
+ email ||
40
+ name ||
41
+ (providerId ? `microsoft_active_directory_${providerId}` : "");
42
+ const normalized = source
43
+ .trim()
44
+ .toLowerCase()
45
+ .replace(/[^a-z0-9@._-]+/g, "_")
46
+ .replace(/^_+|_+$/g, "");
47
+ return normalized || "microsoft_active_directory_user";
48
+ }
49
+
33
50
  private async resolveUniqueUsername(
34
51
  preferredUsername: string,
35
52
  // fallbackUsername: string,
@@ -397,6 +414,50 @@ export class UserService extends CRUDService<User> {
397
414
  return user;
398
415
  }
399
416
 
417
+ async resolveUserOnOauthMicrosoftActiveDirectory(
418
+ oauthUserDto: OauthUserDto,
419
+ ): Promise<User> {
420
+ const user = await this.repo.findOne({
421
+ where: {
422
+ email: oauthUserDto.email,
423
+ },
424
+ relations: {
425
+ roles: true,
426
+ },
427
+ });
428
+
429
+ if (!user) {
430
+ const newUser = new User();
431
+ newUser.username = oauthUserDto.email;
432
+ newUser.email = oauthUserDto.email;
433
+ newUser.fullName = oauthUserDto.name;
434
+ newUser.lastLoginProvider = oauthUserDto.provider;
435
+ newUser.accessCode = oauthUserDto.accessCode;
436
+ newUser.microsoftActiveDirectoryAccessToken = oauthUserDto.accessToken;
437
+ newUser.microsoftActiveDirectoryId = oauthUserDto.providerId;
438
+ newUser.microsoftActiveDirectoryProfilePicture = oauthUserDto.picture;
439
+
440
+ const savedUser = await this.repo.save(newUser);
441
+
442
+ await this.initializeRolesForNewUser(
443
+ [this.settingService.getConfigValue<SolidCoreSetting>("defaultRole")],
444
+ savedUser,
445
+ );
446
+ } else {
447
+ const entity = await this.repo.preload({
448
+ id: user.id,
449
+ lastLoginProvider: oauthUserDto.provider,
450
+ accessCode: oauthUserDto.accessCode,
451
+ microsoftActiveDirectoryAccessToken: oauthUserDto.accessToken,
452
+ microsoftActiveDirectoryId: oauthUserDto.providerId,
453
+ microsoftActiveDirectoryProfilePicture: oauthUserDto.picture,
454
+ });
455
+
456
+ await this.repo.save(entity);
457
+ }
458
+ return user;
459
+ }
460
+
400
461
  async findUsersByRole(
401
462
  roleName: string,
402
463
  relations: any = {},
@@ -66,8 +66,10 @@ import { ActionMetadataService } from "./services/action-metadata.service";
66
66
 
67
67
  import { FacebookAuthenticationController } from "./controllers/facebook-authentication.controller";
68
68
  import { MicrosoftAuthenticationController } from "./controllers/microsoft-authentication.controller";
69
+ import { MicrosoftActiveDirectoryAuthenticationController } from "./controllers/microsoft-active-directory-authentication.controller";
69
70
  import { FacebookOAuthStrategy } from "./passport-strategies/facebook-oauth.strategy";
70
71
  import { MicrosoftOAuthStrategy } from "./passport-strategies/microsoft-oauth.strategy";
72
+ import { MicrosoftActiveDirectoryOAuthStrategy } from "./passport-strategies/microsoft-active-directory-oauth.strategy";
71
73
 
72
74
  import { GupshupOtpWhatsappService } from "./services/whatsapp/GupshupOtpWhatsappService";
73
75
  import { MetaCloudWhatsappService } from "./services/whatsapp/MetaCloudWhatsappService";
@@ -464,6 +466,7 @@ import { DashboardUserLayoutRepository } from './repositories/dashboard-user-lay
464
466
  GoogleAuthenticationController,
465
467
  FacebookAuthenticationController,
466
468
  MicrosoftAuthenticationController,
469
+ MicrosoftActiveDirectoryAuthenticationController,
467
470
  ImportTransactionController,
468
471
  ImportTransactionErrorLogController,
469
472
  ListOfValuesController,
@@ -637,6 +640,7 @@ import { DashboardUserLayoutRepository } from './repositories/dashboard-user-lay
637
640
  GoogleOauthStrategy,
638
641
  FacebookOAuthStrategy,
639
642
  MicrosoftOAuthStrategy,
643
+ MicrosoftActiveDirectoryOAuthStrategy,
640
644
  UserRegistrationListener,
641
645
  TestQueuePublisher,
642
646
  TestQueueSubscriber,