@things-factory/auth-azure-ad 8.0.0-beta.0 → 8.0.0-beta.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/auth-azure-ad",
3
- "version": "8.0.0-beta.0",
3
+ "version": "8.0.0-beta.2",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "dist-client/index.js",
6
6
  "things-factory": true,
@@ -29,10 +29,10 @@
29
29
  "dependencies": {
30
30
  "@operato/graphql": "^8.0.0-beta",
31
31
  "@operato/shell": "^8.0.0-beta",
32
- "@things-factory/auth-base": "^8.0.0-beta.0",
33
- "@things-factory/shell": "^8.0.0-beta.0",
32
+ "@things-factory/auth-base": "^8.0.0-beta.2",
33
+ "@things-factory/shell": "^8.0.0-beta.2",
34
34
  "passport": "^0.7.0",
35
35
  "passport-azure-ad": "^4.3.5"
36
36
  },
37
- "gitHead": "add6fb8224b2cb19cbea47bed6a5ecb0424c9a28"
37
+ "gitHead": "f03431a09435511b2595515658f9cb8f78ba4ebb"
38
38
  }
@@ -1,11 +0,0 @@
1
- {
2
- "extends": "../../tsconfig-base.json",
3
- "compilerOptions": {
4
- "strict": true,
5
- "declaration": true,
6
- "module": "esnext",
7
- "outDir": "../dist-client",
8
- "baseUrl": "./"
9
- },
10
- "include": ["./**/*"]
11
- }
@@ -1,30 +0,0 @@
1
- import fetch from 'node-fetch'
2
-
3
- import { AuthProvider } from '@things-factory/auth-base'
4
-
5
- const authorityHostUrl = 'https://login.microsoftonline.com'
6
-
7
- export async function getAccessToken(authProvider: AuthProvider): Promise<string | null> {
8
- try {
9
- const { clientId, clientSecret, tenantId } = authProvider
10
-
11
- const authorityUrl = `${authorityHostUrl}/${tenantId}`
12
- const resource = 'https://graph.microsoft.com'
13
- const OAUTH2_URL = `${authorityUrl}/oauth2/token?api-version=1.0`
14
-
15
- const tokenResponse = await fetch(OAUTH2_URL, {
16
- method: 'POST',
17
- headers: {
18
- 'Content-Type': 'application/x-www-form-urlencoded'
19
- },
20
- body: `client_id=${clientId}&resource=${resource}&client_secret=${clientSecret}&grant_type=client_credentials`
21
- })
22
-
23
- const result = await tokenResponse.json()
24
- const { access_token } = result
25
- return access_token
26
- } catch (error) {
27
- console.error('Error fetching access token:', error)
28
- return null
29
- }
30
- }
@@ -1,34 +0,0 @@
1
- import fetch from 'node-fetch'
2
-
3
- import { getAccessToken } from './get-access-token'
4
- import { AuthProvider } from '@things-factory/auth-base'
5
-
6
- var clientState
7
-
8
- export function getClientState() {
9
- return clientState
10
- }
11
-
12
- export async function msgraphSubscriptions(authProvider: AuthProvider) {
13
- const accessToken = await getAccessToken(authProvider)
14
- const expirationDateTime = new Date(Date.now() + 3 * 24 * 60 * 60 * 1000)
15
-
16
- clientState = `${authProvider.domainId}:${authProvider.id}` // TODO make secure
17
-
18
- const response = await fetch('https://graph.microsoft.com/v1.0/subscriptions', {
19
- method: 'POST',
20
- headers: {
21
- Authorization: `Bearer ${accessToken}`,
22
- 'Content-Type': 'application/json'
23
- },
24
- body: JSON.stringify({
25
- changeType: 'created,updated',
26
- notificationUrl: 'YOUR_WEBHOOK_ENDPOINT_BASE' + '/webhook/azure-ad-users',
27
- resource: '/users',
28
- expirationDateTime: expirationDateTime.toISOString() /* 최대 3일 이내, UTC 타임으로 설정 */,
29
- clientState
30
- })
31
- })
32
-
33
- const data = await response.json()
34
- }
@@ -1,129 +0,0 @@
1
- import fetch from 'node-fetch'
2
-
3
- import { AuthProvider, User, UserStatus, UsersAuthProviders } from '@things-factory/auth-base'
4
- import { getAccessToken } from './get-access-token'
5
-
6
- export async function syncAllUserInfo(authProvider: AuthProvider, context: ResolverContext) {
7
- const accessToken = await getAccessToken(authProvider)
8
-
9
- if (!accessToken) {
10
- throw new Error('Failed to obtain access token')
11
- }
12
-
13
- const latestData = await fetch('https://graph.microsoft.com/v1.0/users', {
14
- headers: {
15
- Authorization: `Bearer ${accessToken}`
16
- }
17
- }).then(res => res.json())
18
-
19
- for (const user of latestData.value) {
20
- if (user.mail) {
21
- await updateUserInfo(authProvider, user, context)
22
- }
23
- }
24
- }
25
-
26
- export async function updateUserInfo(authProvider: AuthProvider, userInfo, context: ResolverContext) {
27
- const { tx, domain, user } = context.state
28
-
29
- // {
30
- // "id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
31
- // "businessPhones": [
32
- // "+1 412 555 0109"
33
- // ],
34
- // "displayName": "John Doe",
35
- // "givenName": "John",
36
- // "jobTitle": "Developer",
37
- // "mail": "john.doe@contoso.com",
38
- // "mobilePhone": "+1 412 555 0109",
39
- // "officeLocation": "Floor 2",
40
- // "preferredLanguage": "en-US",
41
- // "surname": "Doe",
42
- // "userPrincipalName": "john.doe@contoso.com"
43
- // }
44
- const {
45
- id,
46
- businessPhones,
47
- displayName,
48
- givenName,
49
- surname,
50
- jobTitle,
51
- mail,
52
- mobilePhone,
53
- officeLocation,
54
- preferredLanguage,
55
- userPricipalName
56
- } = userInfo
57
-
58
- const repository = tx.getRepository(User)
59
-
60
- // 1. 사용자를 찾는다.(email 정보로)
61
- const existingUser = await repository.findOne({
62
- where: { email: mail },
63
- relations: ['domains']
64
- })
65
-
66
- if (!existingUser) {
67
- // 2. 사용자가 없다면, 생성한다.
68
- const salt = User.generateSalt()
69
-
70
- /* normally they don't login with this password. */
71
- const password = salt
72
-
73
- const createdUser = await repository.save({
74
- id,
75
- name: displayName,
76
- email: mail,
77
- creator: user,
78
- updater: user,
79
- domains: domain ? [domain] : [],
80
- roles: [],
81
- salt,
82
- userType: 'user',
83
- // ssoId: id,
84
- locale: preferredLanguage,
85
- status: UserStatus.ACTIVATED,
86
- passwordUpdatedAt: new Date(),
87
- password: User.encode(password, salt)
88
- })
89
-
90
- await tx.getRepository(UsersAuthProviders).save({
91
- domain,
92
- user: createdUser,
93
- authProvider,
94
- ssoId: id
95
- })
96
-
97
- return createdUser
98
- } else {
99
- // 3. 사용자가 있다면, 업데이트한다.
100
- const { domains } = existingUser
101
-
102
- if (!domains.find(existing => existing.id == domain.id)) {
103
- domains.push(domain)
104
- }
105
-
106
- const updatedUser = await repository.save({
107
- ...existingUser,
108
- name: displayName,
109
- // ssoId: id,
110
- domains,
111
- locale: preferredLanguage,
112
- updater: user
113
- })
114
-
115
- const usersAuthProviders = await tx.getRepository(UsersAuthProviders).findOne({
116
- where: { domain: { id: domain.id }, user: { id: updatedUser.id }, authProvider: { id: authProvider.id } }
117
- })
118
-
119
- await tx.getRepository(UsersAuthProviders).save({
120
- ...usersAuthProviders,
121
- domain,
122
- user: updatedUser,
123
- authProvider,
124
- ssoId: id
125
- })
126
-
127
- return updatedUser
128
- }
129
- }
package/server/index.ts DELETED
@@ -1,18 +0,0 @@
1
- export * from './middlewares'
2
- export * from './service'
3
-
4
- import './routes'
5
-
6
- import { AuthProvider } from '@things-factory/auth-base'
7
- import { syncAllUserInfo } from './controllers/sync-user-info'
8
-
9
- AuthProvider.register('azure', {
10
- type: 'azure',
11
- description: 'Authentication Provider for Azure Active Directory',
12
- help: '',
13
- parameterSpec: null,
14
- async synchronizeUsers(authProvider: AuthProvider, context: ResolverContext): Promise<boolean> {
15
- await syncAllUserInfo(authProvider, context)
16
- return true
17
- }
18
- })
@@ -1,80 +0,0 @@
1
- import passport from 'koa-passport'
2
- import { OIDCStrategy } from 'passport-azure-ad'
3
-
4
- import { config } from '@things-factory/env'
5
- import { User } from '@things-factory/auth-base'
6
-
7
- const SSOAzureADConfig = config.get('sso/azure/config')
8
-
9
- if (SSOAzureADConfig) {
10
- passport.use(
11
- new OIDCStrategy(
12
- {
13
- allowHttpForRedirectUrl: true,
14
- ...SSOAzureADConfig,
15
- validateIssuer: false /* multi-tenant application 이므로 */,
16
- passReqToCallback: true,
17
- isB2C: false,
18
- scope: ['openid', 'email', 'profile']
19
- },
20
- async (req, iss, sub, profile, accessToken, refreshToken, done) => {
21
- if (!profile.oid || !profile._json?.email) {
22
- return done(new Error('No oid or no email found'), null)
23
- }
24
-
25
- try {
26
- const { email } = profile._json
27
-
28
- const user = await User.checkAuthWithEmail({ email })
29
-
30
- const { id, userType, status } = user
31
-
32
- return done(null, {
33
- id,
34
- userType,
35
- status
36
- })
37
- } catch (error) {
38
- return done(error)
39
- }
40
- }
41
- )
42
- )
43
- }
44
-
45
- export async function azureADMiddleware(context, next) {
46
- const { path } = context
47
- const { user } = context.state
48
-
49
- if (user) {
50
- return await next()
51
- }
52
-
53
- await passport.authenticate('azuread-openidconnect', {
54
- failureRedirect: '/auth/signin',
55
- successRedirect: '/auth/checkin'
56
- })(context, next)
57
- }
58
-
59
- export async function azureADSubscriptionMiddleware(context, next) {
60
- const { path } = context
61
- const { user } = context.state
62
-
63
- if (user) {
64
- return await next()
65
- }
66
-
67
- await passport.authenticate('azuread-openidconnect', {
68
- failureRedirect: '/auth/signin',
69
- successRedirect: '/auth/checkin'
70
- })(context)
71
-
72
- /* 다음은, websocket 에서 인증을 처리하기 위한 작업임. */
73
- const passportObject = context.session?.passport
74
- if (passportObject) {
75
- const userEntity = await User.checkAuth(passportObject?.user)
76
- context.state.user = userEntity
77
- }
78
-
79
- return await next()
80
- }
@@ -1,13 +0,0 @@
1
- import { config } from '@things-factory/env'
2
-
3
- const SSOAzureADConfig = config.get('sso/azure/config')
4
-
5
- import { azureADSubscriptionMiddleware } from './azure-ad-authenticate-middleware'
6
-
7
- if (SSOAzureADConfig) {
8
- process.on('bootstrap-module-subscription' as any, (app, subscriptionMiddleware) => {
9
- subscriptionMiddleware.unshift(azureADSubscriptionMiddleware)
10
- })
11
- }
12
-
13
- export * from './azure-ad-authenticate-middleware'
@@ -1,11 +0,0 @@
1
- import Router from 'koa-router'
2
-
3
- import passport from 'koa-passport'
4
- import { azureADMiddleware } from '../middlewares'
5
-
6
- export const authAzureADRouter = new Router()
7
-
8
- authAzureADRouter.post(
9
- '/auth/callback-azure-ad',
10
- passport.authenticate('azuread-openidconnect', { failureRedirect: '/auth/signin', successRedirect: '/auth/checkin' })
11
- )
@@ -1,96 +0,0 @@
1
- import Router from 'koa-router'
2
- import fetch from 'node-fetch'
3
-
4
- import { getRepository, getDataSource } from '@things-factory/shell'
5
-
6
- import { updateUserInfo } from '../controllers/sync-user-info'
7
- import { getAccessToken } from '../controllers/get-access-token'
8
- import { AuthProvider } from '@things-factory/auth-base'
9
-
10
- export const authAzureADWebhookRouter = new Router()
11
-
12
- // Microsoft Graph에서 발송되는 알림을 처리하기 위한 타입 정의
13
- interface Notification {
14
- resourceData: {
15
- id: string
16
- }
17
- }
18
-
19
- authAzureADWebhookRouter.post('/webhook/azure-ad-users', async context => {
20
- const notifications: Notification[] = context.request.body.value
21
- const clientState = context.request.body.clientState
22
- const [domainId, authProviderId] = clientState?.split(':') || []
23
-
24
- if (!domainId || !authProviderId) {
25
- context.status = 401 // unauthorized
26
- context.body = 'Invalid ClientState'
27
- return
28
- }
29
-
30
- await getDataSource('tx').transaction(async tx => {
31
- const wrap = context.app.createContext(context.req || {}, context.res || {})
32
-
33
- wrap.state = {
34
- ...context.state,
35
- domain: null,
36
- tx
37
- }
38
- wrap.t = context.t
39
-
40
- const authProvider = await tx.getRepository(AuthProvider).findOne({
41
- where: {
42
- domain: { id: domainId },
43
- id: authProviderId
44
- }
45
- })
46
-
47
- if (!authProvider) {
48
- context.status = 401 // unauthorized
49
- context.body = 'Invalid ClientState'
50
- return
51
- }
52
-
53
- const accessToken = await getAccessToken(authProvider)
54
-
55
- for (const notification of notifications) {
56
- const userId = notification.resourceData.id
57
-
58
- try {
59
- const userResponse = await fetch(`https://graph.microsoft.com/v1.0/users/${userId}`, {
60
- headers: {
61
- Authorization: `Bearer ${accessToken}`
62
- }
63
- })
64
-
65
- if (!userResponse.ok) {
66
- throw new Error(`Failed to fetch user data: ${userResponse.statusText}`)
67
- }
68
-
69
- const userInfo = await userResponse.json()
70
-
71
- // {
72
- // "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users/$entity",
73
- // "businessPhones": ["+1 412 555 0109"],
74
- // "displayName": "Megan Bowen",
75
- // "givenName": "Megan",
76
- // "jobTitle": "Auditor",
77
- // "mail": "MeganB@M365x214355.onmicrosoft.com",
78
- // "mobilePhone": "+1 412 555 0109",
79
- // "officeLocation": "12/1110",
80
- // "preferredLanguage": "en-US",
81
- // "surname": "Bowen",
82
- // "userPrincipalName": "MeganB@M365x214355.onmicrosoft.com",
83
- // "id": "48d31887-5fad-4d73-a9f5-3c356e68a038"
84
- // }
85
-
86
- // webhook 호출로부터 호출한 tenant를 알 수 있다면, 매핑된 도메인 정보를 포함할 수 있을 것 같은데..
87
-
88
- await updateUserInfo(authProvider, userInfo, wrap)
89
- } catch (error) {
90
- console.error('Error fetching user data from Microsoft Graph:', error)
91
- }
92
- }
93
- })
94
-
95
- context.status = 202
96
- })
@@ -1,2 +0,0 @@
1
- export * from './auth-azure-ad-router'
2
- export * from './auth-azure-ad-webhook-router'
package/server/routes.ts DELETED
@@ -1,29 +0,0 @@
1
- import { config } from '@things-factory/env'
2
- import { setAccessTokenCookie } from '@things-factory/auth-base'
3
-
4
- const SSOAzureADConfig = config.get('sso/azure/config')
5
-
6
- import { azureADMiddleware } from './middlewares'
7
- import { authAzureADRouter, authAzureADWebhookRouter } from './router'
8
-
9
- if (SSOAzureADConfig) {
10
- process.on('bootstrap-collect-sso-middleware' as any, (_, ssoMiddlewares) => {
11
- ssoMiddlewares.push(azureADMiddleware)
12
- })
13
-
14
- process.on('bootstrap-module-domain-public-route' as any, (app, domainPublicRouter) => {
15
- domainPublicRouter.use(authAzureADRouter.routes(), authAzureADRouter.allowedMethods())
16
- domainPublicRouter.use(authAzureADWebhookRouter.routes(), authAzureADWebhookRouter.allowedMethods())
17
- })
18
-
19
- process.on('bootstrap-module-global-public-route' as any, (app, globalPublicRouter) => {
20
- globalPublicRouter.get('/auth/signin-with-azure', azureADMiddleware, async context => {
21
- const { user } = context.state
22
-
23
- const token = await user.sign()
24
- setAccessTokenCookie(context, token)
25
-
26
- context.redirect('/auth/checkin')
27
- })
28
- })
29
- }
@@ -1,24 +0,0 @@
1
- import { Resolver, Mutation, Arg, Ctx, Directive } from 'type-graphql'
2
-
3
- import { syncAllUserInfo } from '../../controllers/sync-user-info'
4
-
5
- @Resolver()
6
- export class AuthAzureAdMutation {
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
-
13
- // return true
14
- // }
15
-
16
- @Directive('@transaction')
17
- @Directive('@privilege(superUserGranted:true)')
18
- @Mutation(returns => Boolean, { description: 'To subscribe azure active directory users' })
19
- async subscribeAzureADUsers(@Ctx() context: ResolverContext): Promise<boolean> {
20
- const { domain, user, tx } = context.state
21
-
22
- return true
23
- }
24
- }
@@ -1,3 +0,0 @@
1
- import { AuthAzureAdMutation } from './auth-azure-ad-mutation'
2
-
3
- export const resolvers = [AuthAzureAdMutation]
@@ -1,19 +0,0 @@
1
- /* EXPORT ENTITY TYPES */
2
-
3
- /* IMPORT ENTITIES AND RESOLVERS */
4
- import { resolvers as AuthAzureAdResolvers } from './auth-azure-ad'
5
-
6
- export const entities = [
7
- /* ENTITIES */
8
- ]
9
-
10
- export const subscribers = [
11
- /* SUBSCRIBERS */
12
- ]
13
-
14
- export const schema = {
15
- resolverClasses: [
16
- /* RESOLVER CLASSES */
17
- ...AuthAzureAdResolvers
18
- ]
19
- }
@@ -1,10 +0,0 @@
1
- {
2
- "extends": "../../tsconfig-base.json",
3
- "compilerOptions": {
4
- "strict": false,
5
- "module": "commonjs",
6
- "outDir": "../dist-server",
7
- "baseUrl": "./"
8
- },
9
- "include": ["./**/*"]
10
- }