@things-factory/auth-google 6.1.188 → 6.1.189

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-google",
3
- "version": "6.1.188",
3
+ "version": "6.1.189",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "dist-client/index.js",
6
6
  "things-factory": true,
@@ -29,10 +29,11 @@
29
29
  "dependencies": {
30
30
  "@operato/graphql": "^1.0.0",
31
31
  "@operato/shell": "^1.0.0",
32
- "@things-factory/auth-base": "^6.1.186",
33
- "@things-factory/shell": "^6.1.186",
32
+ "@things-factory/auth-base": "^6.1.189",
33
+ "@things-factory/shell": "^6.1.189",
34
+ "googleapis": "^126.0.1",
34
35
  "passport": "^0.6.0",
35
36
  "passport-google-oauth20": "^2.0.0"
36
37
  },
37
- "gitHead": "ae62986a327cfe158261052f0ae4f4586d4de55c"
38
+ "gitHead": "0a7d81fcf229582aa643223c56ece75a0432f7d8"
38
39
  }
@@ -0,0 +1,22 @@
1
+ import { google } from 'googleapis'
2
+
3
+ import { AuthProvider } from '@things-factory/auth-base'
4
+
5
+ export async function getOauth2Client(authProvider: AuthProvider): Promise<any> {
6
+ try {
7
+ const { privateKey } = authProvider
8
+
9
+ const serviceAccount = JSON.parse(privateKey)
10
+
11
+ const client = new google.auth.JWT(serviceAccount.client_email, null, serviceAccount.private_key, [
12
+ 'https://www.googleapis.com/auth/admin.directory.user'
13
+ ])
14
+
15
+ await client.authorize()
16
+
17
+ return client
18
+ } catch (error) {
19
+ console.error('Error fetching oauth2 client:', error)
20
+ return null
21
+ }
22
+ }
@@ -1,34 +1,35 @@
1
1
  import fetch from 'node-fetch'
2
2
 
3
- import { getAccessToken } from './get-access-token'
3
+ import { getOauth2Client } from './get-oauth2-client'
4
4
  import { AuthProvider } from '@things-factory/auth-base'
5
5
 
6
+ /* TODO Implement with google */
6
7
  var clientState
7
8
 
8
9
  export function getClientState() {
9
10
  return clientState
10
11
  }
11
12
 
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)
13
+ export async function googleSubscriptions(authProvider: AuthProvider) {
14
+ const client = await getOauth2Client(authProvider)
15
+ // const expirationDateTime = new Date(Date.now() + 3 * 24 * 60 * 60 * 1000)
15
16
 
16
- clientState = `${authProvider.domainId}:${authProvider.id}` // TODO make secure
17
+ // clientState = `${authProvider.domainId}:${authProvider.id}` // TODO make secure
17
18
 
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/google-users',
27
- resource: '/users',
28
- expirationDateTime: expirationDateTime.toISOString() /* 최대 3일 이내, UTC 타임으로 설정 */,
29
- clientState
30
- })
31
- })
19
+ // const response = await fetch('https://graph.microsoft.com/v1.0/subscriptions', {
20
+ // method: 'POST',
21
+ // headers: {
22
+ // Authorization: `Bearer ${accessToken}`,
23
+ // 'Content-Type': 'application/json'
24
+ // },
25
+ // body: JSON.stringify({
26
+ // changeType: 'created,updated',
27
+ // notificationUrl: 'YOUR_WEBHOOK_ENDPOINT_BASE' + '/webhook/google-users',
28
+ // resource: '/users',
29
+ // expirationDateTime: expirationDateTime.toISOString() /* 최대 3일 이내, UTC 타임으로 설정 */,
30
+ // clientState
31
+ // })
32
+ // })
32
33
 
33
- const data = await response.json()
34
+ // const data = await response.json()
34
35
  }
@@ -1,25 +1,36 @@
1
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'
2
+ import { google } from 'googleapis'
3
+
4
+ import {
5
+ AuthProvider,
6
+ User,
7
+ UserStatus,
8
+ UsersAuthProviders,
9
+ encryptState,
10
+ decryptState
11
+ } from '@things-factory/auth-base'
12
+ import { config } from '@things-factory/env'
13
+ import { getOauth2Client } from './get-oauth2-client'
5
14
 
6
15
  export async function syncAllUserInfo(authProvider: AuthProvider, context: ResolverContext) {
7
- const accessToken = await getAccessToken(authProvider)
16
+ const oauth2Client = await getOauth2Client(authProvider)
8
17
 
9
- if (!accessToken) {
10
- throw new Error('Failed to obtain access token')
11
- }
18
+ const service = google.admin({ version: 'directory_v1', auth: oauth2Client })
12
19
 
13
- const latestData = await fetch('https://graph.microsoft.com/v1.0/users', {
14
- headers: {
15
- Authorization: `Bearer ${accessToken}`
16
- }
17
- }).then(res => res.json())
20
+ const res = await service.users.list({
21
+ customer: 'my_customer' /* 'my_customer'는 G Suite 도메인의 모든 사용자를 나타내는 특수 문자열입니다 */,
22
+ maxResults: 10,
23
+ orderBy: 'email'
24
+ })
18
25
 
19
- for (const user of latestData.value) {
20
- if (user.mail) {
26
+ const users = res.data.users
27
+
28
+ if (users && users.length) {
29
+ for (const user of users) {
21
30
  await updateUserInfo(authProvider, user, context)
22
31
  }
32
+ } else {
33
+ console.log('No users found.')
23
34
  }
24
35
  }
25
36
 
@@ -27,39 +38,57 @@ export async function updateUserInfo(authProvider: AuthProvider, userInfo, conte
27
38
  const { tx, domain, user } = context.state
28
39
 
29
40
  // {
30
- // "id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
31
- // "businessPhones": [
32
- // "+1 412 555 0109"
41
+ // "kind": "admin#directory#user",
42
+ // "id": "user's unique ID",
43
+ // "etag": "etag of this resource",
44
+ // "primaryEmail": "user@example.com",
45
+ // "name": {
46
+ // "givenName": "First",
47
+ // "familyName": "Last",
48
+ // "fullName": "First Last"
49
+ // },
50
+ // "isAdmin": false,
51
+ // "isDelegatedAdmin": false,
52
+ // "lastLoginTime": "2021-04-05T12:07:41.000Z",
53
+ // "creationTime": "2017-05-26T10:57:00.000Z",
54
+ // "agreedToTerms": true,
55
+ // "suspended": false,
56
+ // "archived": false,
57
+ // "changePasswordAtNextLogin": false,
58
+ // "ipWhitelisted": false,
59
+ // "emails": [
60
+ // {
61
+ // "address": "user@example.com",
62
+ // "type": "work",
63
+ // "customType": "",
64
+ // "primary": true
65
+ // }
66
+ // ],
67
+ // "organizations": [
68
+ // {
69
+ // "name": "Department Name",
70
+ // "title": "Job Title",
71
+ // "type": "work",
72
+ // "primary": true
73
+ // }
74
+ // ],
75
+ // "phones": [
76
+ // {
77
+ // "value": "+1 xxx-xxx-xxxx",
78
+ // "type": "work",
79
+ // "primary": true
80
+ // }
33
81
  // ],
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"
82
+ // "aliases": ["alias@example.com"],
83
+ // // ... 다른 필드들
43
84
  // }
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
85
+ const { id, name, primaryEmail } = userInfo
57
86
 
58
87
  const repository = tx.getRepository(User)
59
88
 
60
89
  // 1. 사용자를 찾는다.(email 정보로)
61
90
  const existingUser = await repository.findOne({
62
- where: { email: mail },
91
+ where: { email: primaryEmail },
63
92
  relations: ['domains']
64
93
  })
65
94
 
@@ -72,16 +101,15 @@ export async function updateUserInfo(authProvider: AuthProvider, userInfo, conte
72
101
 
73
102
  const createdUser = await repository.save({
74
103
  id,
75
- name: displayName,
76
- email: mail,
104
+ name: name.fullName,
105
+ email: primaryEmail,
77
106
  creator: user,
78
107
  updater: user,
79
108
  domains: domain ? [domain] : [],
80
109
  roles: [],
81
110
  salt,
82
111
  userType: 'user',
83
- // ssoId: id,
84
- locale: preferredLanguage,
112
+ // locale: preferredLanguage,
85
113
  status: UserStatus.ACTIVATED,
86
114
  passwordUpdatedAt: new Date(),
87
115
  password: User.encode(password, salt)
@@ -105,10 +133,9 @@ export async function updateUserInfo(authProvider: AuthProvider, userInfo, conte
105
133
 
106
134
  const updatedUser = await repository.save({
107
135
  ...existingUser,
108
- name: displayName,
109
- // ssoId: id,
136
+ name: name.fullName,
110
137
  domains,
111
- locale: preferredLanguage,
138
+ // locale: preferredLanguage,
112
139
  updater: user
113
140
  })
114
141
 
@@ -3,11 +3,13 @@ import Router from 'koa-router'
3
3
  import passport from 'koa-passport'
4
4
  import { authGoogleMiddleware } from '../middlewares'
5
5
 
6
+ import { encryptState, decryptState } from '@things-factory/auth-base'
7
+
6
8
  export const authGoogleRouter = new Router()
7
9
 
8
10
  authGoogleRouter.get(
9
11
  '/auth/callback-auth-google',
10
- passport.authenticate('google', { failureRedirect: '/auth/signin' }),
12
+ passport.authenticate('google', { failureRedirect: '/auth/signin', successRedirect: '/auth/checkin' }),
11
13
  ctx => {
12
14
  ctx.redirect('/auth/checkin')
13
15
  }
@@ -4,17 +4,18 @@ import fetch from 'node-fetch'
4
4
  import { getRepository, getDataSource } from '@things-factory/shell'
5
5
 
6
6
  import { updateUserInfo } from '../controllers/sync-user-info'
7
- import { getAccessToken } from '../controllers/get-access-token'
7
+ import { getOauth2Client } from '../controllers/get-oauth2-client'
8
8
  import { AuthProvider } from '@things-factory/auth-base'
9
9
 
10
10
  export const authGoogleWebhookRouter = new Router()
11
11
 
12
12
  // Microsoft Graph에서 발송되는 알림을 처리하기 위한 타입 정의
13
- interface Notification {
14
- resourceData: {
15
- id: string
16
- }
17
- }
13
+ // TODO implement with google
14
+ // interface Notification {
15
+ // resourceData: {
16
+ // id: string
17
+ // }
18
+ // }
18
19
 
19
20
  authGoogleWebhookRouter.post('/webhook/google-users', async context => {
20
21
  const notifications: Notification[] = context.request.body.value
@@ -50,46 +51,46 @@ authGoogleWebhookRouter.post('/webhook/google-users', async context => {
50
51
  return
51
52
  }
52
53
 
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
- }
54
+ const oauth2Client = await getOauth2Client(authProvider)
55
+
56
+ // for (const notification of notifications) {
57
+ // const userId = notification.resourceData.id
58
+
59
+ // try {
60
+ // const userResponse = await fetch(`https://graph.microsoft.com/v1.0/users/${userId}`, {
61
+ // headers: {
62
+ // Authorization: `Bearer ${accessToken}`
63
+ // }
64
+ // })
65
+
66
+ // if (!userResponse.ok) {
67
+ // throw new Error(`Failed to fetch user data: ${userResponse.statusText}`)
68
+ // }
69
+
70
+ // const userInfo = await userResponse.json()
71
+
72
+ // // {
73
+ // // "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users/$entity",
74
+ // // "businessPhones": ["+1 412 555 0109"],
75
+ // // "displayName": "Megan Bowen",
76
+ // // "givenName": "Megan",
77
+ // // "jobTitle": "Auditor",
78
+ // // "mail": "MeganB@M365x214355.onmicrosoft.com",
79
+ // // "mobilePhone": "+1 412 555 0109",
80
+ // // "officeLocation": "12/1110",
81
+ // // "preferredLanguage": "en-US",
82
+ // // "surname": "Bowen",
83
+ // // "userPrincipalName": "MeganB@M365x214355.onmicrosoft.com",
84
+ // // "id": "48d31887-5fad-4d73-a9f5-3c356e68a038"
85
+ // // }
86
+
87
+ // // webhook 호출로부터 호출한 tenant를 알 수 있다면, 매핑된 도메인 정보를 포함할 수 있을 것 같은데..
88
+
89
+ // await updateUserInfo(authProvider, userInfo, wrap)
90
+ // } catch (error) {
91
+ // console.error('Error fetching user data from Microsoft Graph:', error)
92
+ // }
93
+ // }
93
94
  })
94
95
 
95
96
  context.status = 202
@@ -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
- }