@things-factory/auth-azure-ad 6.1.185 → 6.1.186

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 (31) hide show
  1. package/config/config.development.js +24 -15
  2. package/config/config.production.js +24 -15
  3. package/dist-server/controllers/get-access-token.js +6 -7
  4. package/dist-server/controllers/get-access-token.js.map +1 -1
  5. package/dist-server/controllers/subscribe-users-change.js +3 -4
  6. package/dist-server/controllers/subscribe-users-change.js.map +1 -1
  7. package/dist-server/controllers/sync-user-info.js +25 -8
  8. package/dist-server/controllers/sync-user-info.js.map +1 -1
  9. package/dist-server/index.js +12 -0
  10. package/dist-server/index.js.map +1 -1
  11. package/dist-server/middlewares/azure-ad-authenticate-middleware.js +2 -2
  12. package/dist-server/middlewares/azure-ad-authenticate-middleware.js.map +1 -1
  13. package/dist-server/middlewares/index.js +1 -1
  14. package/dist-server/middlewares/index.js.map +1 -1
  15. package/dist-server/router/auth-azure-ad-webhook-router.js +51 -40
  16. package/dist-server/router/auth-azure-ad-webhook-router.js.map +1 -1
  17. package/dist-server/routes.js +10 -1
  18. package/dist-server/routes.js.map +1 -1
  19. package/dist-server/service/auth-azure-ad/auth-azure-ad-mutation.js +7 -14
  20. package/dist-server/service/auth-azure-ad/auth-azure-ad-mutation.js.map +1 -1
  21. package/dist-server/tsconfig.tsbuildinfo +1 -1
  22. package/package.json +4 -4
  23. package/server/controllers/get-access-token.ts +9 -8
  24. package/server/controllers/subscribe-users-change.ts +4 -4
  25. package/server/controllers/sync-user-info.ts +39 -10
  26. package/server/index.ts +14 -0
  27. package/server/middlewares/azure-ad-authenticate-middleware.ts +2 -1
  28. package/server/middlewares/index.ts +1 -1
  29. package/server/router/auth-azure-ad-webhook-router.ts +62 -48
  30. package/server/routes.ts +13 -1
  31. package/server/service/auth-azure-ad/auth-azure-ad-mutation.ts +7 -7
@@ -1,16 +1,25 @@
1
- module.exports = {
2
- // SSOAzureAD: {
3
- // tenantID: 'your_tenant_id',
4
- // clientID: 'your_client_id',
5
- // clientSecret: 'your_client_secret',
6
- // identityMetadata: 'https://login.microsoftonline.com/your_tenant_id/v2.0/.well-known/openid-configuration',
7
- // responseType: 'code id_token',
8
- // responseMode: 'form_post',
9
- // redirectUrl: 'http://localhost:3000/auth/openid/return',
10
- // allowHttpForRedirectUrl: true,
11
- // validateIssuer: false,
12
- // passReqToCallback: false
13
- // // useCookieInsteadOfSession: true,
14
- // // cookieEncryptionKeys: [{ key: 'your_cookie_encryption_key', iv: 'your_cookie_encryption_iv' }]
15
- // }
1
+ /**
2
+ * SSO configuration for Azure Active Directory
3
+ *
4
+ {
5
+ sso: {
6
+ azure: {
7
+ title: 'Microsoft Entra ID',
8
+ link: '/auth/signin-with-azure', // signin-link
9
+ config: {
10
+ clientID: 'your_client_id',
11
+ clientSecret: 'your_client_secret',
12
+ identityMetadata: 'https://login.microsoftonline.com/your_tenant_id/v2.0/.well-known/openid-configuration',
13
+ responseType: 'code id_token',
14
+ responseMode: 'form_post',
15
+ redirectUrl: 'http://localhost:3000/auth/openid/return',
16
+ allowHttpForRedirectUrl: true,
17
+ validateIssuer: false,
18
+ passReqToCallback: false
19
+ }
20
+ }
21
+ }
16
22
  }
23
+ */
24
+
25
+ module.exports = {}
@@ -1,16 +1,25 @@
1
- module.exports = {
2
- // SSOAzureAD: {
3
- // tenantID: 'your_tenant_id',
4
- // clientID: 'your_client_id',
5
- // clientSecret: 'your_client_secret',
6
- // identityMetadata: 'https://login.microsoftonline.com/your_tenant_id/v2.0/.well-known/openid-configuration',
7
- // responseType: 'code id_token',
8
- // responseMode: 'form_post',
9
- // redirectUrl: 'http://localhost:3000/auth/openid/return',
10
- // allowHttpForRedirectUrl: true,
11
- // validateIssuer: false,
12
- // passReqToCallback: false
13
- // // useCookieInsteadOfSession: true,
14
- // // cookieEncryptionKeys: [{ key: 'your_cookie_encryption_key', iv: 'your_cookie_encryption_iv' }]
15
- // }
1
+ /**
2
+ * SSO configuration for Azure Active Directory
3
+ *
4
+ {
5
+ sso: {
6
+ azure: {
7
+ title: 'Microsoft Entra ID',
8
+ link: '/auth/signin-with-azure', // signin-link
9
+ config: {
10
+ clientID: 'your_client_id',
11
+ clientSecret: 'your_client_secret',
12
+ identityMetadata: 'https://login.microsoftonline.com/your_tenant_id/v2.0/.well-known/openid-configuration',
13
+ responseType: 'code id_token',
14
+ responseMode: 'form_post',
15
+ redirectUrl: 'http://localhost:3000/auth/openid/return',
16
+ allowHttpForRedirectUrl: true,
17
+ validateIssuer: false,
18
+ passReqToCallback: false
19
+ }
20
+ }
21
+ }
16
22
  }
23
+ */
24
+
25
+ module.exports = {}
@@ -3,20 +3,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getAccessToken = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const node_fetch_1 = tslib_1.__importDefault(require("node-fetch"));
6
- const env_1 = require("@things-factory/env");
7
- const SSOAzureADConfig = env_1.config.get('SSOAzureAD');
8
6
  const authorityHostUrl = 'https://login.microsoftonline.com';
9
- const authorityUrl = `${authorityHostUrl}/${SSOAzureADConfig === null || SSOAzureADConfig === void 0 ? void 0 : SSOAzureADConfig.tenantID}`;
10
- const resource = 'https://graph.microsoft.com';
11
- const OAUTH2_URL = `${authorityUrl}/oauth2/token?api-version=1.0`;
12
- async function getAccessToken() {
7
+ async function getAccessToken(authProvider) {
13
8
  try {
9
+ const { clientId, clientSecret, tenantId } = authProvider;
10
+ const authorityUrl = `${authorityHostUrl}/${tenantId}`;
11
+ const resource = 'https://graph.microsoft.com';
12
+ const OAUTH2_URL = `${authorityUrl}/oauth2/token?api-version=1.0`;
14
13
  const tokenResponse = await (0, node_fetch_1.default)(OAUTH2_URL, {
15
14
  method: 'POST',
16
15
  headers: {
17
16
  'Content-Type': 'application/x-www-form-urlencoded'
18
17
  },
19
- body: `client_id=${SSOAzureADConfig.clientID}&resource=${resource}&client_secret=${SSOAzureADConfig.clientSecret}&grant_type=client_credentials`
18
+ body: `client_id=${clientId}&resource=${resource}&client_secret=${clientSecret}&grant_type=client_credentials`
20
19
  });
21
20
  const result = await tokenResponse.json();
22
21
  const { access_token } = result;
@@ -1 +1 @@
1
- {"version":3,"file":"get-access-token.js","sourceRoot":"","sources":["../../server/controllers/get-access-token.ts"],"names":[],"mappings":";;;;AAAA,oEAA8B;AAE9B,6CAA4C;AAE5C,MAAM,gBAAgB,GAAG,YAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;AAEjD,MAAM,gBAAgB,GAAG,mCAAmC,CAAA;AAC5D,MAAM,YAAY,GAAG,GAAG,gBAAgB,IAAI,gBAAgB,aAAhB,gBAAgB,uBAAhB,gBAAgB,CAAE,QAAQ,EAAE,CAAA;AACxE,MAAM,QAAQ,GAAG,6BAA6B,CAAA;AAC9C,MAAM,UAAU,GAAG,GAAG,YAAY,+BAA+B,CAAA;AAE1D,KAAK,UAAU,cAAc;IAClC,IAAI;QACF,MAAM,aAAa,GAAG,MAAM,IAAA,oBAAK,EAAC,UAAU,EAAE;YAC5C,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,mCAAmC;aACpD;YACD,IAAI,EAAE,aAAa,gBAAgB,CAAC,QAAQ,aAAa,QAAQ,kBAAkB,gBAAgB,CAAC,YAAY,gCAAgC;SACjJ,CAAC,CAAA;QAEF,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,EAAE,CAAA;QACzC,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,CAAA;QAC/B,OAAO,YAAY,CAAA;KACpB;IAAC,OAAO,KAAK,EAAE;QACd,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,KAAK,CAAC,CAAA;QACpD,OAAO,IAAI,CAAA;KACZ;AACH,CAAC;AAjBD,wCAiBC","sourcesContent":["import fetch from 'node-fetch'\n\nimport { config } from '@things-factory/env'\n\nconst SSOAzureADConfig = config.get('SSOAzureAD')\n\nconst authorityHostUrl = 'https://login.microsoftonline.com'\nconst authorityUrl = `${authorityHostUrl}/${SSOAzureADConfig?.tenantID}`\nconst resource = 'https://graph.microsoft.com'\nconst OAUTH2_URL = `${authorityUrl}/oauth2/token?api-version=1.0`\n\nexport async function getAccessToken(): Promise<string | null> {\n try {\n const tokenResponse = await fetch(OAUTH2_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded'\n },\n body: `client_id=${SSOAzureADConfig.clientID}&resource=${resource}&client_secret=${SSOAzureADConfig.clientSecret}&grant_type=client_credentials`\n })\n\n const result = await tokenResponse.json()\n const { access_token } = result\n return access_token\n } catch (error) {\n console.error('Error fetching access token:', error)\n return null\n }\n}\n"]}
1
+ {"version":3,"file":"get-access-token.js","sourceRoot":"","sources":["../../server/controllers/get-access-token.ts"],"names":[],"mappings":";;;;AAAA,oEAA8B;AAI9B,MAAM,gBAAgB,GAAG,mCAAmC,CAAA;AAErD,KAAK,UAAU,cAAc,CAAC,YAA0B;IAC7D,IAAI;QACF,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,GAAG,YAAY,CAAA;QAEzD,MAAM,YAAY,GAAG,GAAG,gBAAgB,IAAI,QAAQ,EAAE,CAAA;QACtD,MAAM,QAAQ,GAAG,6BAA6B,CAAA;QAC9C,MAAM,UAAU,GAAG,GAAG,YAAY,+BAA+B,CAAA;QAEjE,MAAM,aAAa,GAAG,MAAM,IAAA,oBAAK,EAAC,UAAU,EAAE;YAC5C,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,mCAAmC;aACpD;YACD,IAAI,EAAE,aAAa,QAAQ,aAAa,QAAQ,kBAAkB,YAAY,gCAAgC;SAC/G,CAAC,CAAA;QAEF,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,EAAE,CAAA;QACzC,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,CAAA;QAC/B,OAAO,YAAY,CAAA;KACpB;IAAC,OAAO,KAAK,EAAE;QACd,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,KAAK,CAAC,CAAA;QACpD,OAAO,IAAI,CAAA;KACZ;AACH,CAAC;AAvBD,wCAuBC","sourcesContent":["import fetch from 'node-fetch'\n\nimport { AuthProvider } from '@things-factory/auth-base'\n\nconst authorityHostUrl = 'https://login.microsoftonline.com'\n\nexport async function getAccessToken(authProvider: AuthProvider): Promise<string | null> {\n try {\n const { clientId, clientSecret, tenantId } = authProvider\n\n const authorityUrl = `${authorityHostUrl}/${tenantId}`\n const resource = 'https://graph.microsoft.com'\n const OAUTH2_URL = `${authorityUrl}/oauth2/token?api-version=1.0`\n\n const tokenResponse = await fetch(OAUTH2_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded'\n },\n body: `client_id=${clientId}&resource=${resource}&client_secret=${clientSecret}&grant_type=client_credentials`\n })\n\n const result = await tokenResponse.json()\n const { access_token } = result\n return access_token\n } catch (error) {\n console.error('Error fetching access token:', error)\n return null\n }\n}\n"]}
@@ -3,17 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.msgraphSubscriptions = exports.getClientState = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const node_fetch_1 = tslib_1.__importDefault(require("node-fetch"));
6
- const crypto_1 = require("crypto");
7
6
  const get_access_token_1 = require("./get-access-token");
8
7
  var clientState;
9
8
  function getClientState() {
10
9
  return clientState;
11
10
  }
12
11
  exports.getClientState = getClientState;
13
- async function msgraphSubscriptions() {
14
- const accessToken = await (0, get_access_token_1.getAccessToken)();
12
+ async function msgraphSubscriptions(authProvider) {
13
+ const accessToken = await (0, get_access_token_1.getAccessToken)(authProvider);
15
14
  const expirationDateTime = new Date(Date.now() + 3 * 24 * 60 * 60 * 1000);
16
- clientState = (0, crypto_1.randomBytes)(20).toString('hex');
15
+ clientState = `${authProvider.domainId}:${authProvider.id}`; // TODO make secure
17
16
  const response = await (0, node_fetch_1.default)('https://graph.microsoft.com/v1.0/subscriptions', {
18
17
  method: 'POST',
19
18
  headers: {
@@ -1 +1 @@
1
- {"version":3,"file":"subscribe-users-change.js","sourceRoot":"","sources":["../../server/controllers/subscribe-users-change.ts"],"names":[],"mappings":";;;;AAAA,oEAA8B;AAC9B,mCAAoC;AAEpC,yDAAmD;AAEnD,IAAI,WAAW,CAAA;AAEf,SAAgB,cAAc;IAC5B,OAAO,WAAW,CAAA;AACpB,CAAC;AAFD,wCAEC;AAEM,KAAK,UAAU,oBAAoB;IACxC,MAAM,WAAW,GAAG,MAAM,IAAA,iCAAc,GAAE,CAAA;IAC1C,MAAM,kBAAkB,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAA;IAEzE,WAAW,GAAG,IAAA,oBAAW,EAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IAE7C,MAAM,QAAQ,GAAG,MAAM,IAAA,oBAAK,EAAC,gDAAgD,EAAE;QAC7E,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,aAAa,EAAE,UAAU,WAAW,EAAE;YACtC,cAAc,EAAE,kBAAkB;SACnC;QACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,UAAU,EAAE,iBAAiB;YAC7B,eAAe,EAAE,4BAA4B,GAAG,yBAAyB;YACzE,QAAQ,EAAE,QAAQ;YAClB,kBAAkB,EAAE,kBAAkB,CAAC,WAAW,EAAE,CAAC,2BAA2B;YAChF,WAAW;SACZ,CAAC;KACH,CAAC,CAAA;IAEF,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;AACpC,CAAC;AAtBD,oDAsBC","sourcesContent":["import fetch from 'node-fetch'\nimport { randomBytes } from 'crypto'\n\nimport { getAccessToken } from './get-access-token'\n\nvar clientState\n\nexport function getClientState() {\n return clientState\n}\n\nexport async function msgraphSubscriptions() {\n const accessToken = await getAccessToken()\n const expirationDateTime = new Date(Date.now() + 3 * 24 * 60 * 60 * 1000)\n\n clientState = randomBytes(20).toString('hex')\n\n const response = await fetch('https://graph.microsoft.com/v1.0/subscriptions', {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n changeType: 'created,updated',\n notificationUrl: 'YOUR_WEBHOOK_ENDPOINT_BASE' + '/webhook/azure-ad-users',\n resource: '/users',\n expirationDateTime: expirationDateTime.toISOString() /* 최대 3일 이내, UTC 타임으로 설정 */,\n clientState\n })\n })\n\n const data = await response.json()\n}\n"]}
1
+ {"version":3,"file":"subscribe-users-change.js","sourceRoot":"","sources":["../../server/controllers/subscribe-users-change.ts"],"names":[],"mappings":";;;;AAAA,oEAA8B;AAE9B,yDAAmD;AAGnD,IAAI,WAAW,CAAA;AAEf,SAAgB,cAAc;IAC5B,OAAO,WAAW,CAAA;AACpB,CAAC;AAFD,wCAEC;AAEM,KAAK,UAAU,oBAAoB,CAAC,YAA0B;IACnE,MAAM,WAAW,GAAG,MAAM,IAAA,iCAAc,EAAC,YAAY,CAAC,CAAA;IACtD,MAAM,kBAAkB,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAA;IAEzE,WAAW,GAAG,GAAG,YAAY,CAAC,QAAQ,IAAI,YAAY,CAAC,EAAE,EAAE,CAAA,CAAC,mBAAmB;IAE/E,MAAM,QAAQ,GAAG,MAAM,IAAA,oBAAK,EAAC,gDAAgD,EAAE;QAC7E,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,aAAa,EAAE,UAAU,WAAW,EAAE;YACtC,cAAc,EAAE,kBAAkB;SACnC;QACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,UAAU,EAAE,iBAAiB;YAC7B,eAAe,EAAE,4BAA4B,GAAG,yBAAyB;YACzE,QAAQ,EAAE,QAAQ;YAClB,kBAAkB,EAAE,kBAAkB,CAAC,WAAW,EAAE,CAAC,2BAA2B;YAChF,WAAW;SACZ,CAAC;KACH,CAAC,CAAA;IAEF,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;AACpC,CAAC;AAtBD,oDAsBC","sourcesContent":["import fetch from 'node-fetch'\n\nimport { getAccessToken } from './get-access-token'\nimport { AuthProvider } from '@things-factory/auth-base'\n\nvar clientState\n\nexport function getClientState() {\n return clientState\n}\n\nexport async function msgraphSubscriptions(authProvider: AuthProvider) {\n const accessToken = await getAccessToken(authProvider)\n const expirationDateTime = new Date(Date.now() + 3 * 24 * 60 * 60 * 1000)\n\n clientState = `${authProvider.domainId}:${authProvider.id}` // TODO make secure\n\n const response = await fetch('https://graph.microsoft.com/v1.0/subscriptions', {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n changeType: 'created,updated',\n notificationUrl: 'YOUR_WEBHOOK_ENDPOINT_BASE' + '/webhook/azure-ad-users',\n resource: '/users',\n expirationDateTime: expirationDateTime.toISOString() /* 최대 3일 이내, UTC 타임으로 설정 */,\n clientState\n })\n })\n\n const data = await response.json()\n}\n"]}
@@ -5,8 +5,8 @@ const tslib_1 = require("tslib");
5
5
  const node_fetch_1 = tslib_1.__importDefault(require("node-fetch"));
6
6
  const auth_base_1 = require("@things-factory/auth-base");
7
7
  const get_access_token_1 = require("./get-access-token");
8
- async function syncAllUserInfo(context) {
9
- const accessToken = await (0, get_access_token_1.getAccessToken)();
8
+ async function syncAllUserInfo(authProvider, context) {
9
+ const accessToken = await (0, get_access_token_1.getAccessToken)(authProvider);
10
10
  if (!accessToken) {
11
11
  throw new Error('Failed to obtain access token');
12
12
  }
@@ -18,11 +18,11 @@ async function syncAllUserInfo(context) {
18
18
  latestData.value
19
19
  .filter(({ mail }) => mail)
20
20
  .forEach(async (user) => {
21
- await updateUserInfo(user, context);
21
+ await updateUserInfo(authProvider, user, context);
22
22
  });
23
23
  }
24
24
  exports.syncAllUserInfo = syncAllUserInfo;
25
- async function updateUserInfo(userInfo, context) {
25
+ async function updateUserInfo(authProvider, userInfo, context) {
26
26
  const { tx, domain, user } = context.state;
27
27
  // {
28
28
  // "id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
@@ -51,7 +51,7 @@ async function updateUserInfo(userInfo, context) {
51
51
  const salt = auth_base_1.User.generateSalt();
52
52
  /* normally they don't login with this password. */
53
53
  const password = salt;
54
- return await repository.save({
54
+ const createdUser = await repository.save({
55
55
  id,
56
56
  name: displayName,
57
57
  email: mail,
@@ -61,17 +61,34 @@ async function updateUserInfo(userInfo, context) {
61
61
  roles: [],
62
62
  salt,
63
63
  userType: 'user',
64
- ssoId: id,
64
+ // ssoId: id,
65
65
  locale: preferredLanguage,
66
66
  status: auth_base_1.UserStatus.ACTIVATED,
67
67
  passwordUpdatedAt: new Date(),
68
68
  password: auth_base_1.User.encode(password, salt)
69
69
  });
70
- repository.save({});
70
+ await tx.getRepository(auth_base_1.UsersAuthProviders).save({
71
+ domain,
72
+ user: createdUser,
73
+ authProvider,
74
+ ssoId: id
75
+ });
76
+ return createdUser;
71
77
  }
72
78
  else {
73
79
  // 3. 사용자가 있다면, 업데이트한다.
74
- return await repository.save(Object.assign(Object.assign({}, existingUser), { name: displayName, ssoId: id, locale: preferredLanguage, updater: user }));
80
+ const { domains } = existingUser;
81
+ if (!domains.find(existing => existing.id == domain.id)) {
82
+ domains.push(domain);
83
+ }
84
+ const updatedUser = await repository.save(Object.assign(Object.assign({}, existingUser), { name: displayName,
85
+ // ssoId: id,
86
+ domains, locale: preferredLanguage, updater: user }));
87
+ const usersAuthProviders = await tx.getRepository(auth_base_1.UsersAuthProviders).findOne({
88
+ where: { domain: { id: domain.id }, user: { id: updatedUser.id }, authProvider: { id: authProvider.id } }
89
+ });
90
+ await tx.getRepository(auth_base_1.UsersAuthProviders).save(Object.assign(Object.assign({}, usersAuthProviders), { domain, user: updatedUser, authProvider, ssoId: id }));
91
+ return updatedUser;
75
92
  }
76
93
  }
77
94
  exports.updateUserInfo = updateUserInfo;
@@ -1 +1 @@
1
- {"version":3,"file":"sync-user-info.js","sourceRoot":"","sources":["../../server/controllers/sync-user-info.ts"],"names":[],"mappings":"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","sourcesContent":["import fetch from 'node-fetch'\n\nimport { User, UserStatus } from '@things-factory/auth-base'\nimport { getAccessToken } from './get-access-token'\n\nexport async function syncAllUserInfo(context: ResolverContext) {\n const accessToken = await getAccessToken()\n\n if (!accessToken) {\n throw new Error('Failed to obtain access token')\n }\n\n const latestData = await fetch('https://graph.microsoft.com/v1.0/users', {\n headers: {\n Authorization: `Bearer ${accessToken}`\n }\n }).then(res => res.json())\n\n latestData.value\n .filter(({ mail }) => mail)\n .forEach(async user => {\n await updateUserInfo(user, context)\n })\n}\n\nexport async function updateUserInfo(userInfo, context: ResolverContext) {\n const { tx, domain, user } = context.state\n\n // {\n // \"id\": \"d290f1ee-6c54-4b01-90e6-d701748f0851\",\n // \"businessPhones\": [\n // \"+1 412 555 0109\"\n // ],\n // \"displayName\": \"John Doe\",\n // \"givenName\": \"John\",\n // \"jobTitle\": \"Developer\",\n // \"mail\": \"john.doe@contoso.com\",\n // \"mobilePhone\": \"+1 412 555 0109\",\n // \"officeLocation\": \"Floor 2\",\n // \"preferredLanguage\": \"en-US\",\n // \"surname\": \"Doe\",\n // \"userPrincipalName\": \"john.doe@contoso.com\"\n // }\n const {\n id,\n businessPhones,\n displayName,\n givenName,\n surname,\n jobTitle,\n mail,\n mobilePhone,\n officeLocation,\n preferredLanguage,\n userPricipalName\n } = userInfo\n const repository = tx.getRepository(User)\n\n // 1. 사용자를 찾는다.(email 정보로)\n const existingUser = await repository.findOne({\n where: { email: mail },\n relations: ['domains']\n })\n\n if (!existingUser) {\n // 2. 사용자가 없다면, 생성한다.\n const salt = User.generateSalt()\n\n /* normally they don't login with this password. */\n const password = salt\n\n return await repository.save({\n id,\n name: displayName,\n email: mail,\n creator: user,\n updater: user,\n domains: domain ? [domain] : [],\n roles: [],\n salt,\n userType: 'user',\n ssoId: id,\n locale: preferredLanguage,\n status: UserStatus.ACTIVATED,\n passwordUpdatedAt: new Date(),\n password: User.encode(password, salt)\n })\n\n repository.save({})\n } else {\n // 3. 사용자가 있다면, 업데이트한다.\n return await repository.save({\n ...existingUser,\n name: displayName,\n ssoId: id,\n locale: preferredLanguage,\n updater: user\n })\n }\n}\n"]}
1
+ {"version":3,"file":"sync-user-info.js","sourceRoot":"","sources":["../../server/controllers/sync-user-info.ts"],"names":[],"mappings":"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","sourcesContent":["import fetch from 'node-fetch'\n\nimport { AuthProvider, User, UserStatus, UsersAuthProviders } from '@things-factory/auth-base'\nimport { getAccessToken } from './get-access-token'\n\nexport async function syncAllUserInfo(authProvider: AuthProvider, context: ResolverContext) {\n const accessToken = await getAccessToken(authProvider)\n\n if (!accessToken) {\n throw new Error('Failed to obtain access token')\n }\n\n const latestData = await fetch('https://graph.microsoft.com/v1.0/users', {\n headers: {\n Authorization: `Bearer ${accessToken}`\n }\n }).then(res => res.json())\n\n latestData.value\n .filter(({ mail }) => mail)\n .forEach(async user => {\n await updateUserInfo(authProvider, user, context)\n })\n}\n\nexport async function updateUserInfo(authProvider: AuthProvider, userInfo, context: ResolverContext) {\n const { tx, domain, user } = context.state\n\n // {\n // \"id\": \"d290f1ee-6c54-4b01-90e6-d701748f0851\",\n // \"businessPhones\": [\n // \"+1 412 555 0109\"\n // ],\n // \"displayName\": \"John Doe\",\n // \"givenName\": \"John\",\n // \"jobTitle\": \"Developer\",\n // \"mail\": \"john.doe@contoso.com\",\n // \"mobilePhone\": \"+1 412 555 0109\",\n // \"officeLocation\": \"Floor 2\",\n // \"preferredLanguage\": \"en-US\",\n // \"surname\": \"Doe\",\n // \"userPrincipalName\": \"john.doe@contoso.com\"\n // }\n const {\n id,\n businessPhones,\n displayName,\n givenName,\n surname,\n jobTitle,\n mail,\n mobilePhone,\n officeLocation,\n preferredLanguage,\n userPricipalName\n } = userInfo\n\n const repository = tx.getRepository(User)\n\n // 1. 사용자를 찾는다.(email 정보로)\n const existingUser = await repository.findOne({\n where: { email: mail },\n relations: ['domains']\n })\n\n if (!existingUser) {\n // 2. 사용자가 없다면, 생성한다.\n const salt = User.generateSalt()\n\n /* normally they don't login with this password. */\n const password = salt\n\n const createdUser = await repository.save({\n id,\n name: displayName,\n email: mail,\n creator: user,\n updater: user,\n domains: domain ? [domain] : [],\n roles: [],\n salt,\n userType: 'user',\n // ssoId: id,\n locale: preferredLanguage,\n status: UserStatus.ACTIVATED,\n passwordUpdatedAt: new Date(),\n password: User.encode(password, salt)\n })\n\n await tx.getRepository(UsersAuthProviders).save({\n domain,\n user: createdUser,\n authProvider,\n ssoId: id\n })\n\n return createdUser\n } else {\n // 3. 사용자가 있다면, 업데이트한다.\n const { domains } = existingUser\n\n if (!domains.find(existing => existing.id == domain.id)) {\n domains.push(domain)\n }\n\n const updatedUser = await repository.save({\n ...existingUser,\n name: displayName,\n // ssoId: id,\n domains,\n locale: preferredLanguage,\n updater: user\n })\n\n const usersAuthProviders = await tx.getRepository(UsersAuthProviders).findOne({\n where: { domain: { id: domain.id }, user: { id: updatedUser.id }, authProvider: { id: authProvider.id } }\n })\n\n await tx.getRepository(UsersAuthProviders).save({\n ...usersAuthProviders,\n domain,\n user: updatedUser,\n authProvider,\n ssoId: id\n })\n\n return updatedUser\n }\n}\n"]}
@@ -4,4 +4,16 @@ const tslib_1 = require("tslib");
4
4
  tslib_1.__exportStar(require("./middlewares"), exports);
5
5
  tslib_1.__exportStar(require("./service"), exports);
6
6
  require("./routes");
7
+ const auth_base_1 = require("@things-factory/auth-base");
8
+ const sync_user_info_1 = require("./controllers/sync-user-info");
9
+ auth_base_1.AuthProvider.register('azure', {
10
+ type: 'azure',
11
+ description: 'Authentication Provider for Azure Active Directory',
12
+ help: '',
13
+ parameterSpec: null,
14
+ async synchronizeUsers(authProvider, context) {
15
+ await (0, sync_user_info_1.syncAllUserInfo)(authProvider, context);
16
+ return true;
17
+ }
18
+ });
7
19
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;AAAA,wDAA6B;AAC7B,oDAAyB;AAEzB,oBAAiB","sourcesContent":["export * from './middlewares'\nexport * from './service'\n\nimport './routes'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;AAAA,wDAA6B;AAC7B,oDAAyB;AAEzB,oBAAiB;AAEjB,yDAAwD;AACxD,iEAA8D;AAE9D,wBAAY,CAAC,QAAQ,CAAC,OAAO,EAAE;IAC7B,IAAI,EAAE,OAAO;IACb,WAAW,EAAE,oDAAoD;IACjE,IAAI,EAAE,EAAE;IACR,aAAa,EAAE,IAAI;IACnB,KAAK,CAAC,gBAAgB,CAAC,YAA0B,EAAE,OAAwB;QACzE,MAAM,IAAA,gCAAe,EAAC,YAAY,EAAE,OAAO,CAAC,CAAA;QAC5C,OAAO,IAAI,CAAA;IACb,CAAC;CACF,CAAC,CAAA","sourcesContent":["export * from './middlewares'\nexport * from './service'\n\nimport './routes'\n\nimport { AuthProvider } from '@things-factory/auth-base'\nimport { syncAllUserInfo } from './controllers/sync-user-info'\n\nAuthProvider.register('azure', {\n type: 'azure',\n description: 'Authentication Provider for Azure Active Directory',\n help: '',\n parameterSpec: null,\n async synchronizeUsers(authProvider: AuthProvider, context: ResolverContext): Promise<boolean> {\n await syncAllUserInfo(authProvider, context)\n return true\n }\n})\n"]}
@@ -6,9 +6,9 @@ const koa_passport_1 = tslib_1.__importDefault(require("koa-passport"));
6
6
  const passport_azure_ad_1 = require("passport-azure-ad");
7
7
  const env_1 = require("@things-factory/env");
8
8
  const auth_base_1 = require("@things-factory/auth-base");
9
- const SSOAzureADConfig = env_1.config.get('SSOAzureAD');
9
+ const SSOAzureADConfig = env_1.config.get('sso/azure/config');
10
10
  if (SSOAzureADConfig) {
11
- koa_passport_1.default.use(new passport_azure_ad_1.OIDCStrategy(Object.assign(Object.assign({ allowHttpForRedirectUrl: true }, SSOAzureADConfig), { passReqToCallback: true, isB2C: false, scope: ['openid', 'email', 'profile'] }), async (req, iss, sub, profile, accessToken, refreshToken, done) => {
11
+ koa_passport_1.default.use(new passport_azure_ad_1.OIDCStrategy(Object.assign(Object.assign({ allowHttpForRedirectUrl: true }, SSOAzureADConfig), { validateIssuer: false /* multi-tenant application 이므로 */, passReqToCallback: true, isB2C: false, scope: ['openid', 'email', 'profile'] }), async (req, iss, sub, profile, accessToken, refreshToken, done) => {
12
12
  var _a;
13
13
  if (!profile.oid || !((_a = profile._json) === null || _a === void 0 ? void 0 : _a.email)) {
14
14
  return done(new Error('No oid or no email found'), null);
@@ -1 +1 @@
1
- {"version":3,"file":"azure-ad-authenticate-middleware.js","sourceRoot":"","sources":["../../server/middlewares/azure-ad-authenticate-middleware.ts"],"names":[],"mappings":";;;;AAAA,wEAAmC;AACnC,yDAAgD;AAEhD,6CAA4C;AAC5C,yDAAgD;AAEhD,MAAM,gBAAgB,GAAG,YAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;AAEjD,IAAI,gBAAgB,EAAE;IACpB,sBAAQ,CAAC,GAAG,CACV,IAAI,gCAAY,+BAEZ,uBAAuB,EAAE,IAAI,IAC1B,gBAAgB,KACnB,iBAAiB,EAAE,IAAI,EACvB,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,CAAC,KAEvC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,IAAI,EAAE,EAAE;;QAChE,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,CAAA,MAAA,OAAO,CAAC,KAAK,0CAAE,KAAK,CAAA,EAAE;YACzC,OAAO,IAAI,CAAC,IAAI,KAAK,CAAC,0BAA0B,CAAC,EAAE,IAAI,CAAC,CAAA;SACzD;QAED,IAAI;YACF,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;YAE/B,MAAM,IAAI,GAAG,MAAM,gBAAI,CAAC,kBAAkB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;YAErD,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;YAErC,OAAO,IAAI,CAAC,IAAI,EAAE;gBAChB,EAAE;gBACF,QAAQ;gBACR,MAAM;aACP,CAAC,CAAA;SACH;QAAC,OAAO,KAAK,EAAE;YACd,OAAO,IAAI,CAAC,KAAK,CAAC,CAAA;SACnB;IACH,CAAC,CACF,CACF,CAAA;CACF;AAEM,KAAK,UAAU,iBAAiB,CAAC,OAAO,EAAE,IAAI;IACnD,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAA;IACxB,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IAE9B,IAAI,IAAI,EAAE;QACR,OAAO,MAAM,IAAI,EAAE,CAAA;KACpB;IAED,MAAM,sBAAQ,CAAC,YAAY,CAAC,uBAAuB,EAAE;QACnD,eAAe,EAAE,cAAc;QAC/B,eAAe,EAAE,eAAe;KACjC,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;AACnB,CAAC;AAZD,8CAYC;AAEM,KAAK,UAAU,6BAA6B,CAAC,OAAO,EAAE,IAAI;;IAC/D,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAA;IACxB,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IAE9B,IAAI,IAAI,EAAE;QACR,OAAO,MAAM,IAAI,EAAE,CAAA;KACpB;IAED,MAAM,sBAAQ,CAAC,YAAY,CAAC,uBAAuB,EAAE;QACnD,eAAe,EAAE,cAAc;QAC/B,eAAe,EAAE,eAAe;KACjC,CAAC,CAAC,OAAO,CAAC,CAAA;IAEX,wCAAwC;IACxC,MAAM,cAAc,GAAG,MAAA,OAAO,CAAC,OAAO,0CAAE,QAAQ,CAAA;IAChD,IAAI,cAAc,EAAE;QAClB,MAAM,UAAU,GAAG,MAAM,gBAAI,CAAC,SAAS,CAAC,cAAc,aAAd,cAAc,uBAAd,cAAc,CAAE,IAAI,CAAC,CAAA;QAC7D,OAAO,CAAC,KAAK,CAAC,IAAI,GAAG,UAAU,CAAA;KAChC;IAED,OAAO,MAAM,IAAI,EAAE,CAAA;AACrB,CAAC;AArBD,sEAqBC","sourcesContent":["import passport from 'koa-passport'\nimport { OIDCStrategy } from 'passport-azure-ad'\n\nimport { config } from '@things-factory/env'\nimport { User } from '@things-factory/auth-base'\n\nconst SSOAzureADConfig = config.get('SSOAzureAD')\n\nif (SSOAzureADConfig) {\n passport.use(\n new OIDCStrategy(\n {\n allowHttpForRedirectUrl: true,\n ...SSOAzureADConfig,\n passReqToCallback: true,\n isB2C: false,\n scope: ['openid', 'email', 'profile']\n },\n async (req, iss, sub, profile, accessToken, refreshToken, done) => {\n if (!profile.oid || !profile._json?.email) {\n return done(new Error('No oid or no email found'), null)\n }\n\n try {\n const { email } = profile._json\n\n const user = await User.checkAuthWithEmail({ email })\n\n const { id, userType, status } = user\n\n return done(null, {\n id,\n userType,\n status\n })\n } catch (error) {\n return done(error)\n }\n }\n )\n )\n}\n\nexport async function azureADMiddleware(context, next) {\n const { path } = context\n const { user } = context.state\n\n if (user) {\n return await next()\n }\n\n await passport.authenticate('azuread-openidconnect', {\n failureRedirect: '/auth/signin',\n successRedirect: '/auth/checkin'\n })(context, next)\n}\n\nexport async function azureADSubscriptionMiddleware(context, next) {\n const { path } = context\n const { user } = context.state\n\n if (user) {\n return await next()\n }\n\n await passport.authenticate('azuread-openidconnect', {\n failureRedirect: '/auth/signin',\n successRedirect: '/auth/checkin'\n })(context)\n\n /* 다음은, websocket 에서 인증을 처리하기 위한 작업임. */\n const passportObject = context.session?.passport\n if (passportObject) {\n const userEntity = await User.checkAuth(passportObject?.user)\n context.state.user = userEntity\n }\n\n return await next()\n}\n"]}
1
+ {"version":3,"file":"azure-ad-authenticate-middleware.js","sourceRoot":"","sources":["../../server/middlewares/azure-ad-authenticate-middleware.ts"],"names":[],"mappings":";;;;AAAA,wEAAmC;AACnC,yDAAgD;AAEhD,6CAA4C;AAC5C,yDAAgD;AAEhD,MAAM,gBAAgB,GAAG,YAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAA;AAEvD,IAAI,gBAAgB,EAAE;IACpB,sBAAQ,CAAC,GAAG,CACV,IAAI,gCAAY,+BAEZ,uBAAuB,EAAE,IAAI,IAC1B,gBAAgB,KACnB,cAAc,EAAE,KAAK,CAAC,kCAAkC,EACxD,iBAAiB,EAAE,IAAI,EACvB,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,CAAC,KAEvC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,IAAI,EAAE,EAAE;;QAChE,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,CAAA,MAAA,OAAO,CAAC,KAAK,0CAAE,KAAK,CAAA,EAAE;YACzC,OAAO,IAAI,CAAC,IAAI,KAAK,CAAC,0BAA0B,CAAC,EAAE,IAAI,CAAC,CAAA;SACzD;QAED,IAAI;YACF,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;YAE/B,MAAM,IAAI,GAAG,MAAM,gBAAI,CAAC,kBAAkB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;YAErD,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;YAErC,OAAO,IAAI,CAAC,IAAI,EAAE;gBAChB,EAAE;gBACF,QAAQ;gBACR,MAAM;aACP,CAAC,CAAA;SACH;QAAC,OAAO,KAAK,EAAE;YACd,OAAO,IAAI,CAAC,KAAK,CAAC,CAAA;SACnB;IACH,CAAC,CACF,CACF,CAAA;CACF;AAEM,KAAK,UAAU,iBAAiB,CAAC,OAAO,EAAE,IAAI;IACnD,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAA;IACxB,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IAE9B,IAAI,IAAI,EAAE;QACR,OAAO,MAAM,IAAI,EAAE,CAAA;KACpB;IAED,MAAM,sBAAQ,CAAC,YAAY,CAAC,uBAAuB,EAAE;QACnD,eAAe,EAAE,cAAc;QAC/B,eAAe,EAAE,eAAe;KACjC,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;AACnB,CAAC;AAZD,8CAYC;AAEM,KAAK,UAAU,6BAA6B,CAAC,OAAO,EAAE,IAAI;;IAC/D,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAA;IACxB,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IAE9B,IAAI,IAAI,EAAE;QACR,OAAO,MAAM,IAAI,EAAE,CAAA;KACpB;IAED,MAAM,sBAAQ,CAAC,YAAY,CAAC,uBAAuB,EAAE;QACnD,eAAe,EAAE,cAAc;QAC/B,eAAe,EAAE,eAAe;KACjC,CAAC,CAAC,OAAO,CAAC,CAAA;IAEX,wCAAwC;IACxC,MAAM,cAAc,GAAG,MAAA,OAAO,CAAC,OAAO,0CAAE,QAAQ,CAAA;IAChD,IAAI,cAAc,EAAE;QAClB,MAAM,UAAU,GAAG,MAAM,gBAAI,CAAC,SAAS,CAAC,cAAc,aAAd,cAAc,uBAAd,cAAc,CAAE,IAAI,CAAC,CAAA;QAC7D,OAAO,CAAC,KAAK,CAAC,IAAI,GAAG,UAAU,CAAA;KAChC;IAED,OAAO,MAAM,IAAI,EAAE,CAAA;AACrB,CAAC;AArBD,sEAqBC","sourcesContent":["import passport from 'koa-passport'\nimport { OIDCStrategy } from 'passport-azure-ad'\n\nimport { config } from '@things-factory/env'\nimport { User } from '@things-factory/auth-base'\n\nconst SSOAzureADConfig = config.get('sso/azure/config')\n\nif (SSOAzureADConfig) {\n passport.use(\n new OIDCStrategy(\n {\n allowHttpForRedirectUrl: true,\n ...SSOAzureADConfig,\n validateIssuer: false /* multi-tenant application 이므로 */,\n passReqToCallback: true,\n isB2C: false,\n scope: ['openid', 'email', 'profile']\n },\n async (req, iss, sub, profile, accessToken, refreshToken, done) => {\n if (!profile.oid || !profile._json?.email) {\n return done(new Error('No oid or no email found'), null)\n }\n\n try {\n const { email } = profile._json\n\n const user = await User.checkAuthWithEmail({ email })\n\n const { id, userType, status } = user\n\n return done(null, {\n id,\n userType,\n status\n })\n } catch (error) {\n return done(error)\n }\n }\n )\n )\n}\n\nexport async function azureADMiddleware(context, next) {\n const { path } = context\n const { user } = context.state\n\n if (user) {\n return await next()\n }\n\n await passport.authenticate('azuread-openidconnect', {\n failureRedirect: '/auth/signin',\n successRedirect: '/auth/checkin'\n })(context, next)\n}\n\nexport async function azureADSubscriptionMiddleware(context, next) {\n const { path } = context\n const { user } = context.state\n\n if (user) {\n return await next()\n }\n\n await passport.authenticate('azuread-openidconnect', {\n failureRedirect: '/auth/signin',\n successRedirect: '/auth/checkin'\n })(context)\n\n /* 다음은, websocket 에서 인증을 처리하기 위한 작업임. */\n const passportObject = context.session?.passport\n if (passportObject) {\n const userEntity = await User.checkAuth(passportObject?.user)\n context.state.user = userEntity\n }\n\n return await next()\n}\n"]}
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
4
  const env_1 = require("@things-factory/env");
5
- const SSOAzureADConfig = env_1.config.get('SSOAzureAD');
5
+ const SSOAzureADConfig = env_1.config.get('sso/azure/config');
6
6
  const azure_ad_authenticate_middleware_1 = require("./azure-ad-authenticate-middleware");
7
7
  if (SSOAzureADConfig) {
8
8
  process.on('bootstrap-module-subscription', (app, subscriptionMiddleware) => {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/middlewares/index.ts"],"names":[],"mappings":";;;AAAA,6CAA4C;AAE5C,MAAM,gBAAgB,GAAG,YAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;AAEjD,yFAAkF;AAElF,IAAI,gBAAgB,EAAE;IACpB,OAAO,CAAC,EAAE,CAAC,+BAAsC,EAAE,CAAC,GAAG,EAAE,sBAAsB,EAAE,EAAE;QACjF,sBAAsB,CAAC,OAAO,CAAC,gEAA6B,CAAC,CAAA;IAC/D,CAAC,CAAC,CAAA;CACH;AAED,6EAAkD","sourcesContent":["import { config } from '@things-factory/env'\n\nconst SSOAzureADConfig = config.get('SSOAzureAD')\n\nimport { azureADSubscriptionMiddleware } from './azure-ad-authenticate-middleware'\n\nif (SSOAzureADConfig) {\n process.on('bootstrap-module-subscription' as any, (app, subscriptionMiddleware) => {\n subscriptionMiddleware.unshift(azureADSubscriptionMiddleware)\n })\n}\n\nexport * from './azure-ad-authenticate-middleware'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/middlewares/index.ts"],"names":[],"mappings":";;;AAAA,6CAA4C;AAE5C,MAAM,gBAAgB,GAAG,YAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAA;AAEvD,yFAAkF;AAElF,IAAI,gBAAgB,EAAE;IACpB,OAAO,CAAC,EAAE,CAAC,+BAAsC,EAAE,CAAC,GAAG,EAAE,sBAAsB,EAAE,EAAE;QACjF,sBAAsB,CAAC,OAAO,CAAC,gEAA6B,CAAC,CAAA;IAC/D,CAAC,CAAC,CAAA;CACH;AAED,6EAAkD","sourcesContent":["import { config } from '@things-factory/env'\n\nconst SSOAzureADConfig = config.get('sso/azure/config')\n\nimport { azureADSubscriptionMiddleware } from './azure-ad-authenticate-middleware'\n\nif (SSOAzureADConfig) {\n process.on('bootstrap-module-subscription' as any, (app, subscriptionMiddleware) => {\n subscriptionMiddleware.unshift(azureADSubscriptionMiddleware)\n })\n}\n\nexport * from './azure-ad-authenticate-middleware'\n"]}
@@ -7,56 +7,67 @@ const node_fetch_1 = tslib_1.__importDefault(require("node-fetch"));
7
7
  const shell_1 = require("@things-factory/shell");
8
8
  const sync_user_info_1 = require("../controllers/sync-user-info");
9
9
  const get_access_token_1 = require("../controllers/get-access-token");
10
- const subscribe_users_change_1 = require("../controllers/subscribe-users-change");
10
+ const auth_base_1 = require("@things-factory/auth-base");
11
11
  exports.authAzureADWebhookRouter = new koa_router_1.default();
12
12
  exports.authAzureADWebhookRouter.post('/webhook/azure-ad-users', async (context) => {
13
- const accessToken = await (0, get_access_token_1.getAccessToken)();
14
13
  const notifications = context.request.body.value;
15
14
  const clientState = context.request.body.clientState;
16
- if (clientState !== (0, subscribe_users_change_1.getClientState)()) {
17
- // clientState 값이 일치하지 않으면 알림 무시 또는 오류 처리
15
+ const [domainId, authProviderId] = (clientState === null || clientState === void 0 ? void 0 : clientState.split(':')) || [];
16
+ if (!domainId || !authProviderId) {
18
17
  context.status = 401; // unauthorized
19
- context.body = 'Invalid clientState';
18
+ context.body = 'Invalid ClientState';
20
19
  return;
21
20
  }
22
- for (const notification of notifications) {
23
- const userId = notification.resourceData.id;
24
- try {
25
- const userResponse = await (0, node_fetch_1.default)(`https://graph.microsoft.com/v1.0/users/${userId}`, {
26
- headers: {
27
- Authorization: `Bearer ${accessToken}`
28
- }
29
- });
30
- if (!userResponse.ok) {
31
- throw new Error(`Failed to fetch user data: ${userResponse.statusText}`);
21
+ await (0, shell_1.getDataSource)('tx').transaction(async (tx) => {
22
+ const wrap = context.app.createContext(context.req || {}, context.res || {});
23
+ wrap.state = Object.assign(Object.assign({}, context.state), { domain: null, tx });
24
+ wrap.t = context.t;
25
+ const authProvider = await tx.getRepository(auth_base_1.AuthProvider).findOne({
26
+ where: {
27
+ domain: { id: domainId },
28
+ id: authProviderId
32
29
  }
33
- const userInfo = await userResponse.json();
34
- // {
35
- // "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users/$entity",
36
- // "businessPhones": ["+1 412 555 0109"],
37
- // "displayName": "Megan Bowen",
38
- // "givenName": "Megan",
39
- // "jobTitle": "Auditor",
40
- // "mail": "MeganB@M365x214355.onmicrosoft.com",
41
- // "mobilePhone": "+1 412 555 0109",
42
- // "officeLocation": "12/1110",
43
- // "preferredLanguage": "en-US",
44
- // "surname": "Bowen",
45
- // "userPrincipalName": "MeganB@M365x214355.onmicrosoft.com",
46
- // "id": "48d31887-5fad-4d73-a9f5-3c356e68a038"
47
- // }
48
- // webhook 호출로부터 호출한 tenant를 알 수 있다면, 매핑된 도메인 정보를 포함할 수 있을 것 같은데..
49
- return await (0, shell_1.getDataSource)('tx').transaction(async (tx) => {
50
- const wrap = context.app.createContext(context.req || {}, context.res || {});
51
- wrap.state = Object.assign(Object.assign({}, context.state), { domain: null, tx });
52
- wrap.t = context.t;
53
- return await (0, sync_user_info_1.updateUserInfo)(userInfo, wrap);
54
- });
30
+ });
31
+ if (!authProvider) {
32
+ context.status = 401; // unauthorized
33
+ context.body = 'Invalid ClientState';
34
+ return;
55
35
  }
56
- catch (error) {
57
- console.error('Error fetching user data from Microsoft Graph:', error);
36
+ const accessToken = await (0, get_access_token_1.getAccessToken)(authProvider);
37
+ for (const notification of notifications) {
38
+ const userId = notification.resourceData.id;
39
+ try {
40
+ const userResponse = await (0, node_fetch_1.default)(`https://graph.microsoft.com/v1.0/users/${userId}`, {
41
+ headers: {
42
+ Authorization: `Bearer ${accessToken}`
43
+ }
44
+ });
45
+ if (!userResponse.ok) {
46
+ throw new Error(`Failed to fetch user data: ${userResponse.statusText}`);
47
+ }
48
+ const userInfo = await userResponse.json();
49
+ // {
50
+ // "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users/$entity",
51
+ // "businessPhones": ["+1 412 555 0109"],
52
+ // "displayName": "Megan Bowen",
53
+ // "givenName": "Megan",
54
+ // "jobTitle": "Auditor",
55
+ // "mail": "MeganB@M365x214355.onmicrosoft.com",
56
+ // "mobilePhone": "+1 412 555 0109",
57
+ // "officeLocation": "12/1110",
58
+ // "preferredLanguage": "en-US",
59
+ // "surname": "Bowen",
60
+ // "userPrincipalName": "MeganB@M365x214355.onmicrosoft.com",
61
+ // "id": "48d31887-5fad-4d73-a9f5-3c356e68a038"
62
+ // }
63
+ // webhook 호출로부터 호출한 tenant를 알 수 있다면, 매핑된 도메인 정보를 포함할 수 있을 것 같은데..
64
+ await (0, sync_user_info_1.updateUserInfo)(authProvider, userInfo, wrap);
65
+ }
66
+ catch (error) {
67
+ console.error('Error fetching user data from Microsoft Graph:', error);
68
+ }
58
69
  }
59
- }
70
+ });
60
71
  context.status = 202;
61
72
  });
62
73
  //# sourceMappingURL=auth-azure-ad-webhook-router.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth-azure-ad-webhook-router.js","sourceRoot":"","sources":["../../server/router/auth-azure-ad-webhook-router.ts"],"names":[],"mappings":";;;;AAAA,oEAA+B;AAC/B,oEAA8B;AAE9B,iDAAqD;AAErD,kEAA8D;AAC9D,sEAAgE;AAChE,kFAAsE;AAEzD,QAAA,wBAAwB,GAAG,IAAI,oBAAM,EAAE,CAAA;AASpD,gCAAwB,CAAC,IAAI,CAAC,yBAAyB,EAAE,KAAK,EAAC,OAAO,EAAC,EAAE;IACvE,MAAM,WAAW,GAAG,MAAM,IAAA,iCAAc,GAAE,CAAA;IAC1C,MAAM,aAAa,GAAmB,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAA;IAChE,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAA;IAEpD,IAAI,WAAW,KAAK,IAAA,uCAAc,GAAE,EAAE;QACpC,yCAAyC;QACzC,OAAO,CAAC,MAAM,GAAG,GAAG,CAAA,CAAC,eAAe;QACpC,OAAO,CAAC,IAAI,GAAG,qBAAqB,CAAA;QACpC,OAAM;KACP;IAED,KAAK,MAAM,YAAY,IAAI,aAAa,EAAE;QACxC,MAAM,MAAM,GAAG,YAAY,CAAC,YAAY,CAAC,EAAE,CAAA;QAE3C,IAAI;YACF,MAAM,YAAY,GAAG,MAAM,IAAA,oBAAK,EAAC,0CAA0C,MAAM,EAAE,EAAE;gBACnF,OAAO,EAAE;oBACP,aAAa,EAAE,UAAU,WAAW,EAAE;iBACvC;aACF,CAAC,CAAA;YAEF,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE;gBACpB,MAAM,IAAI,KAAK,CAAC,8BAA8B,YAAY,CAAC,UAAU,EAAE,CAAC,CAAA;aACzE;YAED,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,CAAA;YAE1C,IAAI;YACJ,kFAAkF;YAClF,2CAA2C;YAC3C,kCAAkC;YAClC,0BAA0B;YAC1B,2BAA2B;YAC3B,kDAAkD;YAClD,sCAAsC;YACtC,iCAAiC;YACjC,kCAAkC;YAClC,wBAAwB;YACxB,+DAA+D;YAC/D,iDAAiD;YACjD,IAAI;YAEJ,kEAAkE;YAElE,OAAO,MAAM,IAAA,qBAAa,EAAC,IAAI,CAAC,CAAC,WAAW,CAAC,KAAK,EAAC,EAAE,EAAC,EAAE;gBACtD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG,IAAI,EAAE,EAAE,OAAO,CAAC,GAAG,IAAI,EAAE,CAAC,CAAA;gBAE5E,IAAI,CAAC,KAAK,mCACL,OAAO,CAAC,KAAK,KAChB,MAAM,EAAE,IAAI,EACZ,EAAE,GACH,CAAA;gBACD,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAA;gBAElB,OAAO,MAAM,IAAA,+BAAc,EAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;YAC7C,CAAC,CAAC,CAAA;SACH;QAAC,OAAO,KAAK,EAAE;YACd,OAAO,CAAC,KAAK,CAAC,gDAAgD,EAAE,KAAK,CAAC,CAAA;SACvE;KACF;IAED,OAAO,CAAC,MAAM,GAAG,GAAG,CAAA;AACtB,CAAC,CAAC,CAAA","sourcesContent":["import Router from 'koa-router'\nimport fetch from 'node-fetch'\n\nimport { getDataSource } from '@things-factory/shell'\n\nimport { updateUserInfo } from '../controllers/sync-user-info'\nimport { getAccessToken } from '../controllers/get-access-token'\nimport { getClientState } from '../controllers/subscribe-users-change'\n\nexport const authAzureADWebhookRouter = new Router()\n\n// Microsoft Graph에서 발송되는 알림을 처리하기 위한 타입 정의\ninterface Notification {\n resourceData: {\n id: string\n }\n}\n\nauthAzureADWebhookRouter.post('/webhook/azure-ad-users', async context => {\n const accessToken = await getAccessToken()\n const notifications: Notification[] = context.request.body.value\n const clientState = context.request.body.clientState\n\n if (clientState !== getClientState()) {\n // clientState 값이 일치하지 않으면 알림 무시 또는 오류 처리\n context.status = 401 // unauthorized\n context.body = 'Invalid clientState'\n return\n }\n\n for (const notification of notifications) {\n const userId = notification.resourceData.id\n\n try {\n const userResponse = await fetch(`https://graph.microsoft.com/v1.0/users/${userId}`, {\n headers: {\n Authorization: `Bearer ${accessToken}`\n }\n })\n\n if (!userResponse.ok) {\n throw new Error(`Failed to fetch user data: ${userResponse.statusText}`)\n }\n\n const userInfo = await userResponse.json()\n\n // {\n // \"@odata.context\": \"https://graph.microsoft.com/v1.0/$metadata#users/$entity\",\n // \"businessPhones\": [\"+1 412 555 0109\"],\n // \"displayName\": \"Megan Bowen\",\n // \"givenName\": \"Megan\",\n // \"jobTitle\": \"Auditor\",\n // \"mail\": \"MeganB@M365x214355.onmicrosoft.com\",\n // \"mobilePhone\": \"+1 412 555 0109\",\n // \"officeLocation\": \"12/1110\",\n // \"preferredLanguage\": \"en-US\",\n // \"surname\": \"Bowen\",\n // \"userPrincipalName\": \"MeganB@M365x214355.onmicrosoft.com\",\n // \"id\": \"48d31887-5fad-4d73-a9f5-3c356e68a038\"\n // }\n\n // webhook 호출로부터 호출한 tenant를 알 수 있다면, 매핑된 도메인 정보를 포함할 수 있을 것 같은데..\n\n return await getDataSource('tx').transaction(async tx => {\n const wrap = context.app.createContext(context.req || {}, context.res || {})\n\n wrap.state = {\n ...context.state,\n domain: null,\n tx\n }\n wrap.t = context.t\n\n return await updateUserInfo(userInfo, wrap)\n })\n } catch (error) {\n console.error('Error fetching user data from Microsoft Graph:', error)\n }\n }\n\n context.status = 202\n})\n"]}
1
+ {"version":3,"file":"auth-azure-ad-webhook-router.js","sourceRoot":"","sources":["../../server/router/auth-azure-ad-webhook-router.ts"],"names":[],"mappings":"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","sourcesContent":["import Router from 'koa-router'\nimport fetch from 'node-fetch'\n\nimport { getRepository, getDataSource } from '@things-factory/shell'\n\nimport { updateUserInfo } from '../controllers/sync-user-info'\nimport { getAccessToken } from '../controllers/get-access-token'\nimport { AuthProvider } from '@things-factory/auth-base'\n\nexport const authAzureADWebhookRouter = new Router()\n\n// Microsoft Graph에서 발송되는 알림을 처리하기 위한 타입 정의\ninterface Notification {\n resourceData: {\n id: string\n }\n}\n\nauthAzureADWebhookRouter.post('/webhook/azure-ad-users', async context => {\n const notifications: Notification[] = context.request.body.value\n const clientState = context.request.body.clientState\n const [domainId, authProviderId] = clientState?.split(':') || []\n\n if (!domainId || !authProviderId) {\n context.status = 401 // unauthorized\n context.body = 'Invalid ClientState'\n return\n }\n\n await getDataSource('tx').transaction(async tx => {\n const wrap = context.app.createContext(context.req || {}, context.res || {})\n\n wrap.state = {\n ...context.state,\n domain: null,\n tx\n }\n wrap.t = context.t\n\n const authProvider = await tx.getRepository(AuthProvider).findOne({\n where: {\n domain: { id: domainId },\n id: authProviderId\n }\n })\n\n if (!authProvider) {\n context.status = 401 // unauthorized\n context.body = 'Invalid ClientState'\n return\n }\n\n const accessToken = await getAccessToken(authProvider)\n\n for (const notification of notifications) {\n const userId = notification.resourceData.id\n\n try {\n const userResponse = await fetch(`https://graph.microsoft.com/v1.0/users/${userId}`, {\n headers: {\n Authorization: `Bearer ${accessToken}`\n }\n })\n\n if (!userResponse.ok) {\n throw new Error(`Failed to fetch user data: ${userResponse.statusText}`)\n }\n\n const userInfo = await userResponse.json()\n\n // {\n // \"@odata.context\": \"https://graph.microsoft.com/v1.0/$metadata#users/$entity\",\n // \"businessPhones\": [\"+1 412 555 0109\"],\n // \"displayName\": \"Megan Bowen\",\n // \"givenName\": \"Megan\",\n // \"jobTitle\": \"Auditor\",\n // \"mail\": \"MeganB@M365x214355.onmicrosoft.com\",\n // \"mobilePhone\": \"+1 412 555 0109\",\n // \"officeLocation\": \"12/1110\",\n // \"preferredLanguage\": \"en-US\",\n // \"surname\": \"Bowen\",\n // \"userPrincipalName\": \"MeganB@M365x214355.onmicrosoft.com\",\n // \"id\": \"48d31887-5fad-4d73-a9f5-3c356e68a038\"\n // }\n\n // webhook 호출로부터 호출한 tenant를 알 수 있다면, 매핑된 도메인 정보를 포함할 수 있을 것 같은데..\n\n await updateUserInfo(authProvider, userInfo, wrap)\n } catch (error) {\n console.error('Error fetching user data from Microsoft Graph:', error)\n }\n }\n })\n\n context.status = 202\n})\n"]}
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const env_1 = require("@things-factory/env");
4
- const SSOAzureADConfig = env_1.config.get('SSOAzureAD');
4
+ const auth_base_1 = require("@things-factory/auth-base");
5
+ const SSOAzureADConfig = env_1.config.get('sso/azure/config');
5
6
  const middlewares_1 = require("./middlewares");
6
7
  const router_1 = require("./router");
7
8
  if (SSOAzureADConfig) {
@@ -12,5 +13,13 @@ if (SSOAzureADConfig) {
12
13
  domainPublicRouter.use(router_1.authAzureADRouter.routes(), router_1.authAzureADRouter.allowedMethods());
13
14
  domainPublicRouter.use(router_1.authAzureADWebhookRouter.routes(), router_1.authAzureADWebhookRouter.allowedMethods());
14
15
  });
16
+ process.on('bootstrap-module-global-public-route', (app, globalPublicRouter) => {
17
+ globalPublicRouter.get('/auth/signin-with-azure', middlewares_1.azureADMiddleware, async (context) => {
18
+ const { user } = context.state;
19
+ const token = user.sign();
20
+ (0, auth_base_1.setAccessTokenCookie)(context, token);
21
+ context.redirect('/auth/checkin');
22
+ });
23
+ });
15
24
  }
16
25
  //# sourceMappingURL=routes.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"routes.js","sourceRoot":"","sources":["../server/routes.ts"],"names":[],"mappings":";;AAAA,6CAA4C;AAE5C,MAAM,gBAAgB,GAAG,YAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;AAEjD,+CAAiD;AACjD,qCAAsE;AAEtE,IAAI,gBAAgB,EAAE;IACpB,OAAO,CAAC,EAAE,CAAC,kCAAyC,EAAE,CAAC,CAAC,EAAE,cAAc,EAAE,EAAE;QAC1E,cAAc,CAAC,IAAI,CAAC,+BAAiB,CAAC,CAAA;IACxC,CAAC,CAAC,CAAA;IAEF,OAAO,CAAC,EAAE,CAAC,sCAA6C,EAAE,CAAC,GAAG,EAAE,kBAAkB,EAAE,EAAE;QACpF,kBAAkB,CAAC,GAAG,CAAC,0BAAiB,CAAC,MAAM,EAAE,EAAE,0BAAiB,CAAC,cAAc,EAAE,CAAC,CAAA;QACtF,kBAAkB,CAAC,GAAG,CAAC,iCAAwB,CAAC,MAAM,EAAE,EAAE,iCAAwB,CAAC,cAAc,EAAE,CAAC,CAAA;IACtG,CAAC,CAAC,CAAA;CACH","sourcesContent":["import { config } from '@things-factory/env'\n\nconst SSOAzureADConfig = config.get('SSOAzureAD')\n\nimport { azureADMiddleware } from './middlewares'\nimport { authAzureADRouter, authAzureADWebhookRouter } from './router'\n\nif (SSOAzureADConfig) {\n process.on('bootstrap-collect-sso-middleware' as any, (_, ssoMiddlewares) => {\n ssoMiddlewares.push(azureADMiddleware)\n })\n\n process.on('bootstrap-module-domain-public-route' as any, (app, domainPublicRouter) => {\n domainPublicRouter.use(authAzureADRouter.routes(), authAzureADRouter.allowedMethods())\n domainPublicRouter.use(authAzureADWebhookRouter.routes(), authAzureADWebhookRouter.allowedMethods())\n })\n}\n"]}
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../server/routes.ts"],"names":[],"mappings":";;AAAA,6CAA4C;AAC5C,yDAAgE;AAEhE,MAAM,gBAAgB,GAAG,YAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAA;AAEvD,+CAAiD;AACjD,qCAAsE;AAEtE,IAAI,gBAAgB,EAAE;IACpB,OAAO,CAAC,EAAE,CAAC,kCAAyC,EAAE,CAAC,CAAC,EAAE,cAAc,EAAE,EAAE;QAC1E,cAAc,CAAC,IAAI,CAAC,+BAAiB,CAAC,CAAA;IACxC,CAAC,CAAC,CAAA;IAEF,OAAO,CAAC,EAAE,CAAC,sCAA6C,EAAE,CAAC,GAAG,EAAE,kBAAkB,EAAE,EAAE;QACpF,kBAAkB,CAAC,GAAG,CAAC,0BAAiB,CAAC,MAAM,EAAE,EAAE,0BAAiB,CAAC,cAAc,EAAE,CAAC,CAAA;QACtF,kBAAkB,CAAC,GAAG,CAAC,iCAAwB,CAAC,MAAM,EAAE,EAAE,iCAAwB,CAAC,cAAc,EAAE,CAAC,CAAA;IACtG,CAAC,CAAC,CAAA;IAEF,OAAO,CAAC,EAAE,CAAC,sCAA6C,EAAE,CAAC,GAAG,EAAE,kBAAkB,EAAE,EAAE;QACpF,kBAAkB,CAAC,GAAG,CAAC,yBAAyB,EAAE,+BAAiB,EAAE,KAAK,EAAC,OAAO,EAAC,EAAE;YACnF,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;YAE9B,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;YACzB,IAAA,gCAAoB,EAAC,OAAO,EAAE,KAAK,CAAC,CAAA;YAEpC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAA;QACnC,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;CACH","sourcesContent":["import { config } from '@things-factory/env'\nimport { setAccessTokenCookie } from '@things-factory/auth-base'\n\nconst SSOAzureADConfig = config.get('sso/azure/config')\n\nimport { azureADMiddleware } from './middlewares'\nimport { authAzureADRouter, authAzureADWebhookRouter } from './router'\n\nif (SSOAzureADConfig) {\n process.on('bootstrap-collect-sso-middleware' as any, (_, ssoMiddlewares) => {\n ssoMiddlewares.push(azureADMiddleware)\n })\n\n process.on('bootstrap-module-domain-public-route' as any, (app, domainPublicRouter) => {\n domainPublicRouter.use(authAzureADRouter.routes(), authAzureADRouter.allowedMethods())\n domainPublicRouter.use(authAzureADWebhookRouter.routes(), authAzureADWebhookRouter.allowedMethods())\n })\n\n process.on('bootstrap-module-global-public-route' as any, (app, globalPublicRouter) => {\n globalPublicRouter.get('/auth/signin-with-azure', azureADMiddleware, async context => {\n const { user } = context.state\n\n const token = user.sign()\n setAccessTokenCookie(context, token)\n\n context.redirect('/auth/checkin')\n })\n })\n}\n"]}
@@ -3,26 +3,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AuthAzureAdMutation = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const type_graphql_1 = require("type-graphql");
6
- const sync_user_info_1 = require("../../controllers/sync-user-info");
7
6
  let AuthAzureAdMutation = class AuthAzureAdMutation {
8
- async synchronizeAzureADUsers(context) {
9
- (0, sync_user_info_1.syncAllUserInfo)(context);
10
- return true;
11
- }
7
+ // @Directive('@transaction')
8
+ // @Directive('@privilege(superUserGranted:true)')
9
+ // @Mutation(returns => Boolean, { description: 'To synchronize azure active directory users' })
10
+ // async synchronizeAzureADUsers(@Ctx() context: ResolverContext): Promise<boolean> {
11
+ // syncAllUserInfo(context)
12
+ // return true
13
+ // }
12
14
  async subscribeAzureADUsers(context) {
13
15
  const { domain, user, tx } = context.state;
14
16
  return true;
15
17
  }
16
18
  };
17
- tslib_1.__decorate([
18
- (0, type_graphql_1.Directive)('@transaction'),
19
- (0, type_graphql_1.Directive)('@privilege(superUserGranted:true)'),
20
- (0, type_graphql_1.Mutation)(returns => Boolean, { description: 'To synchronize azure active directory users' }),
21
- tslib_1.__param(0, (0, type_graphql_1.Ctx)()),
22
- tslib_1.__metadata("design:type", Function),
23
- tslib_1.__metadata("design:paramtypes", [Object]),
24
- tslib_1.__metadata("design:returntype", Promise)
25
- ], AuthAzureAdMutation.prototype, "synchronizeAzureADUsers", null);
26
19
  tslib_1.__decorate([
27
20
  (0, type_graphql_1.Directive)('@transaction'),
28
21
  (0, type_graphql_1.Directive)('@privilege(superUserGranted:true)'),
@@ -1 +1 @@
1
- {"version":3,"file":"auth-azure-ad-mutation.js","sourceRoot":"","sources":["../../../server/service/auth-azure-ad/auth-azure-ad-mutation.ts"],"names":[],"mappings":";;;;AAAA,+CAAsE;AAEtE,qEAAkE;AAG3D,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IAIxB,AAAN,KAAK,CAAC,uBAAuB,CAAQ,OAAwB;QAC3D,IAAA,gCAAe,EAAC,OAAO,CAAC,CAAA;QAExB,OAAO,IAAI,CAAA;IACb,CAAC;IAKK,AAAN,KAAK,CAAC,qBAAqB,CAAQ,OAAwB;QACzD,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QAE1C,OAAO,IAAI,CAAA;IACb,CAAC;CACF,CAAA;AAdO;IAHL,IAAA,wBAAS,EAAC,cAAc,CAAC;IACzB,IAAA,wBAAS,EAAC,mCAAmC,CAAC;IAC9C,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,6CAA6C,EAAE,CAAC;IAC9D,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;kEAInC;AAKK;IAHL,IAAA,wBAAS,EAAC,cAAc,CAAC;IACzB,IAAA,wBAAS,EAAC,mCAAmC,CAAC;IAC9C,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,2CAA2C,EAAE,CAAC;IAC9D,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;gEAIjC;AAjBU,mBAAmB;IAD/B,IAAA,uBAAQ,GAAE;GACE,mBAAmB,CAkB/B;AAlBY,kDAAmB","sourcesContent":["import { Resolver, Mutation, Arg, Ctx, Directive } from 'type-graphql'\n\nimport { syncAllUserInfo } from '../../controllers/sync-user-info'\n\n@Resolver()\nexport class AuthAzureAdMutation {\n @Directive('@transaction')\n @Directive('@privilege(superUserGranted:true)')\n @Mutation(returns => Boolean, { description: 'To synchronize azure active directory users' })\n async synchronizeAzureADUsers(@Ctx() context: ResolverContext): Promise<boolean> {\n syncAllUserInfo(context)\n\n return true\n }\n\n @Directive('@transaction')\n @Directive('@privilege(superUserGranted:true)')\n @Mutation(returns => Boolean, { description: 'To subscribe azure active directory users' })\n async subscribeAzureADUsers(@Ctx() context: ResolverContext): Promise<boolean> {\n const { domain, user, tx } = context.state\n\n return true\n }\n}\n"]}
1
+ {"version":3,"file":"auth-azure-ad-mutation.js","sourceRoot":"","sources":["../../../server/service/auth-azure-ad/auth-azure-ad-mutation.ts"],"names":[],"mappings":";;;;AAAA,+CAAsE;AAK/D,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IAC9B,6BAA6B;IAC7B,kDAAkD;IAClD,gGAAgG;IAChG,qFAAqF;IACrF,6BAA6B;IAE7B,gBAAgB;IAChB,IAAI;IAKE,AAAN,KAAK,CAAC,qBAAqB,CAAQ,OAAwB;QACzD,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QAE1C,OAAO,IAAI,CAAA;IACb,CAAC;CACF,CAAA;AALO;IAHL,IAAA,wBAAS,EAAC,cAAc,CAAC;IACzB,IAAA,wBAAS,EAAC,mCAAmC,CAAC;IAC9C,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,2CAA2C,EAAE,CAAC;IAC9D,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;gEAIjC;AAjBU,mBAAmB;IAD/B,IAAA,uBAAQ,GAAE;GACE,mBAAmB,CAkB/B;AAlBY,kDAAmB","sourcesContent":["import { Resolver, Mutation, Arg, Ctx, Directive } from 'type-graphql'\n\nimport { syncAllUserInfo } from '../../controllers/sync-user-info'\n\n@Resolver()\nexport class AuthAzureAdMutation {\n // @Directive('@transaction')\n // @Directive('@privilege(superUserGranted:true)')\n // @Mutation(returns => Boolean, { description: 'To synchronize azure active directory users' })\n // async synchronizeAzureADUsers(@Ctx() context: ResolverContext): Promise<boolean> {\n // syncAllUserInfo(context)\n\n // return true\n // }\n\n @Directive('@transaction')\n @Directive('@privilege(superUserGranted:true)')\n @Mutation(returns => Boolean, { description: 'To subscribe azure active directory users' })\n async subscribeAzureADUsers(@Ctx() context: ResolverContext): Promise<boolean> {\n const { domain, user, tx } = context.state\n\n return true\n }\n}\n"]}