@live-change/google-authentication-service 0.8.14 → 0.8.15

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 (2) hide show
  1. package/index.js +341 -172
  2. package/package.json +4 -4
package/index.js CHANGED
@@ -1,51 +1,236 @@
1
1
  import App from '@live-change/framework'
2
2
  const app = App.app()
3
3
 
4
+ import Debug from 'debug'
5
+ const debug = Debug('services:googleAuthentication')
6
+
4
7
  import { OAuth2Client } from 'google-auth-library'
5
8
 
6
- const googClientId = process.env.GOOGLE_CLIENT_ID
7
- const googClient = new OAuth2Client(googClientId)
9
+ import user from '@live-change/user-service'
8
10
 
9
11
  const definition = app.createServiceDefinition({
10
12
  name: "googleAuthentication",
11
- eventSourcing: true
13
+ use: [ user ]
12
14
  })
15
+ const config = definition.config
16
+
17
+ const googleClientId = config.clientId || process.env.GOOGLE_CLIENT_ID
18
+ const googleClient = new OAuth2Client(googleClientId)
13
19
 
20
+ const User = definition.foreignModel("user", "User")
14
21
 
15
- const User = definition.foreignModel("users", "User")
22
+ const googleProperties = {
23
+ email: {
24
+ type: String
25
+ },
26
+ email_verified: {
27
+ type: Boolean
28
+ },
29
+ name: {
30
+ type: String
31
+ },
32
+ given_name: {
33
+ type: String
34
+ },
35
+ family_name: {
36
+ type: String
37
+ },
38
+ picture: {
39
+ type: String
40
+ },
41
+ locale: {
42
+ type: String
43
+ }
44
+ }
16
45
 
17
- const Login = definition.model({
18
- name: "Login",
46
+ const Account = definition.model({
47
+ name: "Account",
19
48
  properties: {
20
- name: {
21
- type: String
49
+ ...googleProperties
50
+ },
51
+ userItem: {
52
+ userReadAccess: () => true
53
+ }
54
+ })
55
+
56
+ definition.event({
57
+ name: 'accountConnected',
58
+ properties: {
59
+ account: {
60
+ type: String,
61
+ validation: ['nonEmpty']
22
62
  },
23
- id: {
24
- type: String
63
+ user: {
64
+ type: User,
65
+ validation: ['nonEmpty']
25
66
  },
26
- email: {
27
- type: String
67
+ data: {
68
+ type: Object,
69
+ properties: googleProperties,
70
+ validation: ['nonEmpty']
71
+ }
72
+ },
73
+ async execute({ user, account, data }) {
74
+ await Account.create({
75
+ ...data,
76
+ id: account,
77
+ user
78
+ })
79
+ }
80
+ })
81
+
82
+ definition.event({
83
+ name: "accountDisconnected",
84
+ properties: {
85
+ account: {
86
+ type: String,
87
+ validation: ['nonEmpty']
28
88
  },
29
89
  user: {
30
- type: User
90
+ type: User,
91
+ validation: ['nonEmpty']
31
92
  }
32
93
  },
33
- indexes: {
34
- byUser: {
35
- property: "user"
94
+ async execute({ account }) {
95
+ await Account.delete(account)
96
+ }
97
+ })
98
+
99
+ definition.trigger({
100
+ name: "signInGoogle",
101
+ properties: {
102
+ account: {
103
+ type: String,
104
+ validation: ['nonEmpty']
36
105
  }
37
106
  },
38
- crud: {
39
- options: {
40
- access: (params, {client, service, visibilityTest}) => {
41
- return client.roles.includes('admin')
107
+ async execute({ account, session }, { service }, _emit) {
108
+ const accountData = await Account.get(account)
109
+ if(!accountData) throw { properties: { email: 'notFound' } }
110
+ const { user } = accountData
111
+ return service.trigger({
112
+ type: 'signIn',
113
+ user, session
114
+ })
115
+ }
116
+ })
117
+
118
+ async function downloadData(user, data, service) {
119
+ await service.trigger({
120
+ type: 'setIdentification',
121
+ sessionOrUserType: 'user',
122
+ sessionOrUser: user,
123
+ overwrite: false,
124
+ name: data.name,
125
+ givenName: data.given_name,
126
+ firstName: data.given_name,
127
+ familyName: data.family_name,
128
+ lastName: data.family_name,
129
+ })
130
+ if(data.picture) {
131
+ const downloadAndUpdateImage = (async () => {
132
+ const picture = await service.trigger('pictures', {
133
+ type: "createPictureFromUrl",
134
+ ownerType: 'user_User',
135
+ owner: user,
136
+ name: "google-profile-picture",
137
+ purpose: "users-updatePicture-picture",
138
+ url: data.picture,
139
+ cropped: true
140
+ })
141
+ await service.trigger({
142
+ type: 'setIdentification',
143
+ sessionOrUserType: 'user',
144
+ sessionOrUser: user,
145
+ overwrite: false,
146
+ picture
147
+ })
148
+ })
149
+ downloadAndUpdateImage()
150
+ }
151
+ if(data.email_verified) {
152
+ await service.trigger({
153
+ type: 'connectEmail',
154
+ email: data.email,
155
+ user
156
+ })
157
+ }
158
+ }
159
+
160
+ definition.trigger({
161
+ name: "connectGoogle",
162
+ properties: {
163
+ user: {
164
+ type: User,
165
+ validation: ['nonEmpty']
166
+ },
167
+ account: {
168
+ type: String,
169
+ validation: ['nonEmpty']
170
+ },
171
+ data: {
172
+ type: Object,
173
+ properties: googleProperties,
174
+ validation: ['nonEmpty']
175
+ },
176
+ transferOwnership: {
177
+ type: Boolean,
178
+ default: false
179
+ }
180
+ },
181
+ async execute({ user, account, data, transferOwnership }, { service }, emit) {
182
+ const accountData = await Account.get(account)
183
+ if(accountData) {
184
+ if(accountData.user !== user) {
185
+ if(transferOwnership) {
186
+ emit({
187
+ 'type': 'userOwnedAccountTransferred',
188
+ account, to: user
189
+ })
190
+ await downloadData(user, data, service)
191
+ return
192
+ }
193
+ throw 'alreadyConnectedElsewhere'
42
194
  }
195
+ throw 'alreadyConnected'
43
196
  }
197
+ emit({
198
+ type: 'accountConnected',
199
+ account, user, data
200
+ })
201
+ await service.trigger({
202
+ type: 'googleConnected',
203
+ user, account, data
204
+ })
205
+ await downloadData(user, data, service)
206
+ }
207
+ })
208
+
209
+ definition.trigger({
210
+ name: "disconnectGoogle",
211
+ properties: {
212
+ account: {
213
+ type: String,
214
+ validation: ['nonEmpty']
215
+ }
216
+ },
217
+ async execute({ account }, { client, service }, emit) {
218
+ const accountData = await Account.get(account)
219
+ if(!accountData) throw 'notFound'
220
+ const { user } = accountData
221
+ emit({
222
+ type: 'accountDisconnected',
223
+ account, user
224
+ })
225
+ await service.trigger({
226
+ type: 'googleDisconnected',
227
+ user, account
228
+ })
44
229
  }
45
230
  })
46
231
 
47
232
  definition.action({
48
- name: "registerOrLogin",
233
+ name: "signIn",
49
234
  properties: {
50
235
  accessToken: {
51
236
  type: String
@@ -55,188 +240,172 @@ definition.action({
55
240
  type: User,
56
241
  idOnly: true
57
242
  },
58
- async execute({ accessToken, userData: userDataParams }, { client, service }, emit) {
59
- const ticket = await googClient.verifyIdToken({
243
+ waitForEvents: true,
244
+ async execute({ accessToken }, { client, service }, emit) {
245
+ const ticket = await googleClient.verifyIdToken({
60
246
  idToken: accessToken,
61
- audience: googClientId
247
+ audience: googleClientId
62
248
  })
63
- const googUser = ticket.getPayload()
64
- console.log("GOOGLE USER", googUser)
65
- const existingLogin = await Login.get(googUser.sub)
66
- if(existingLogin) { /// Login
67
- let userRow = await User.get(existingLogin.user)
68
- const user = existingLogin.user
69
- if(!userRow) throw new Error("internalServerError")
70
- emit("session", [{
71
- type: "loggedIn",
72
- user: existingLogin.user,
73
- session: client.sessionId,
74
- expire: null,
75
- roles: userRow.roles || []
76
- }])
249
+ const googleUser = ticket.getPayload()
250
+ debug("GOOGLE USER", googleUser)
251
+ const account = googleUser.sub
252
+ const existingLogin = await Account.get(account)
253
+ const { session } = client
254
+ if(existingLogin) { /// Sign In
255
+ const { user } = existingLogin
77
256
  await service.trigger({
78
- type: "OnLogin",
79
- user,
80
- session: client.sessionId
81
- })
82
- return existingLogin.user
83
- } else { // Register
84
- const user = app.generateUid()
85
- console.log("PIC", googUser.picture)
86
- let userData = JSON.parse(JSON.stringify({
87
- name: googUser.name,
88
- email: googUser.email,
89
- firstName: googUser.given_name,
90
- lastName: googUser.family_name
91
- }))
92
-
93
- userData = { ...userDataParams, ...userData }
94
-
95
- await service.trigger({
96
- type:"OnRegisterStart",
97
- session: client.sessionId,
98
- user: user
257
+ type: 'signIn',
258
+ user, session
99
259
  })
260
+ return {
261
+ action: 'signIn',
262
+ user
263
+ }
264
+ } else { // Sign up
265
+ throw 'notFound'
266
+ }
267
+ }
268
+ })
100
269
 
101
- emit("googleAuthentication", [{
102
- type: "LoginCreated",
103
- login: googUser.sub,
104
- data: {
105
- id: googUser.sub,
106
- user,
107
- ...userData
108
- }
109
- }])
110
- emit("users", [{
111
- type: "UserCreated",
112
- user,
113
- data: {
114
- userData,
115
- //display: await userDataDefinition.getDisplay({ userData })
116
- // TODO: add identification
117
- }
118
- },{
119
- type: "loginMethodAdded",
120
- user,
121
- method: {
122
- type: "google",
123
- id: googUser.sub,
124
- goog: googUser
125
- }
126
- }])
270
+ definition.action({
271
+ name: "signUp",
272
+ properties: {
273
+ accessToken: {
274
+ type: String
275
+ }
276
+ },
277
+ returns: {
278
+ type: User,
279
+ idOnly: true
280
+ },
281
+ waitForEvents: true,
282
+ async execute({ accessToken }, { client, service }, emit) {
283
+ const ticket = await googleClient.verifyIdToken({
284
+ idToken: accessToken,
285
+ audience: googleClientId
286
+ })
287
+ const googleUser = ticket.getPayload()
288
+ debug("GOOGLE USER", googleUser)
289
+ const account = googleUser.sub
290
+ const existingLogin = await Account.get(account)
291
+ const { session } = client
292
+ if(existingLogin) { /// Sign In
293
+ throw 'alreadyConnected'
294
+ } else { // Sign up
295
+ const user = app.generateUid()
127
296
  await service.trigger({
128
- type:"OnRegister",
129
- session: client.sessionId,
130
- user: user,
131
- userData
297
+ type: 'connectGoogle',
298
+ user, account, data: googleUser
132
299
  })
133
- emit("session", [{
134
- type: "loggedIn",
135
- user,
136
- session: client.sessionId,
137
- expire: null,
138
- roles: []
139
- }])
140
300
  await service.trigger({
141
- type: "OnLogin",
142
- user,
143
- session: client.sessionId
301
+ type: 'signUpAndSignIn',
302
+ user, session
144
303
  })
145
-
146
- // Completly asynchronous
147
- service.triggerService('pictures',{
148
- type: "createPictureFromUrl",
149
- owner: user,
150
- name: "google-profile-picture",
151
- purpose: "users-updatePicture-picture",
152
- url: googUser.picture,
153
- cropped: true
154
- }).then(picture => {
155
- emit('users', [{
156
- type: "UserUpdated",
157
- user,
158
- data: {
159
- userData: {
160
- picture
161
- }
162
- }
163
- }])
164
- }).catch(e => {})
165
-
166
-
167
- return user
304
+ return {
305
+ action: 'signUp',
306
+ user
307
+ }
168
308
  }
169
309
  }
170
-
171
310
  })
172
311
 
173
- /*definition.action({
174
- name: "removeConnection", // override CRUD operation
175
- properties: {},
312
+ definition.action({
313
+ name: "signInOrSignUp",
314
+ properties: {
315
+ accessToken: {
316
+ type: String
317
+ }
318
+ },
176
319
  returns: {
177
320
  type: User,
178
321
  idOnly: true
179
322
  },
180
- async execute({ }, { client, service }, emit) {
181
- if(!client.user) throw new new Error("notAuthorized")
182
- const user = client.user
183
- const results = await (service.dao.get(['database', 'query', service.databaseName, `(${
184
- async (input, output, { user }) =>
185
- await input.table("googleLogin_Login").onChange((obj, oldObj) => {
186
- if(obj && obj.user == user) output.put(obj)
187
- })
188
- })`, { user }]))
189
- if(results.length == 0) throw 'notFound'
190
- let events = []
191
- for(let row of results) {
192
- events.push({
193
- type: "LoginRemoved",
194
- login: row.id
323
+ waitForEvents: true,
324
+ async execute({ accessToken }, { client, service }, emit) {
325
+ const ticket = await googleClient.verifyIdToken({
326
+ idToken: accessToken,
327
+ audience: googleClientId
328
+ })
329
+ const googleUser = ticket.getPayload()
330
+ debug("GOOGLE USER", googleUser)
331
+ const account = googleUser.sub
332
+ const existingLogin = await Account.get(account)
333
+ const { session } = client
334
+ if(existingLogin) { /// Sign In
335
+ const { user } = existingLogin
336
+ await service.trigger({
337
+ type: 'signIn',
338
+ user, session
195
339
  })
340
+ return {
341
+ action: 'signIn',
342
+ user: existingLogin.user
343
+ }
344
+ } else { // Sign up
345
+ const user = app.generateUid()
346
+ await service.trigger({
347
+ type: 'connectGoogle',
348
+ user, account, data: googleUser
349
+ })
350
+ await service.trigger({
351
+ type: 'signUpAndSignIn',
352
+ user, session
353
+ })
354
+ return {
355
+ action: 'signUp',
356
+ user
357
+ }
196
358
  }
197
- emit("facebookLogin", events)
198
359
  }
199
- })*/
360
+ })
200
361
 
201
- definition.event({
202
- name: "UserDeleted",
362
+ definition.action({
363
+ name: "connectGoogle",
203
364
  properties: {
204
- user: {
205
- type: User
365
+ accessToken: {
366
+ type: String
367
+ },
368
+ transferOwnership: {
369
+ type: Boolean,
370
+ default: false
206
371
  }
207
372
  },
208
- async execute({ user }) {
209
- await app.dao.request(['database', 'query'], app.databaseName, `(${
210
- async (input, output, { table, index, user }) => {
211
- const prefix = `"${user}"_`
212
- await (await input.index(index)).range({
213
- gte: prefix,
214
- lte: prefix+"\xFF\xFF\xFF\xFF"
215
- }).onChange((ind, oldInd) => {
216
- if(ind && ind.to) {
217
- output.table(table).delete(ind.to)
218
- }
219
- })
220
- }
221
- })`, { table: Login.tableName, index: Login.tableName + '_byUser', user })
373
+ async execute({ accessToken, transferOwnership }, { client, service }, emit) {
374
+ const ticket = await googleClient.verifyIdToken({
375
+ idToken: accessToken,
376
+ audience: googleClientId
377
+ })
378
+ const user = client.user
379
+ if(!user) throw 'notAuthorized'
380
+ const googleUser = ticket.getPayload()
381
+ debug("GOOGLE USER", googleUser)
382
+ const account = googleUser.sub
383
+ await service.trigger({
384
+ type: 'connectGoogle',
385
+ user, account, data: googleUser,
386
+ transferOwnership
387
+ })
222
388
  }
223
389
  })
224
390
 
225
- definition.trigger({
226
- name: "UserDeleted",
391
+ definition.action({
392
+ name: "disconnectGoogle",
227
393
  properties: {
228
- user: {
229
- type: User,
230
- idOnly: true
394
+ account: {
395
+ type: String,
396
+ validation: ['nonEmpty']
231
397
  }
232
398
  },
233
- async execute({ user }, context, emit) {
234
- emit([{
235
- type: "UserDeleted",
236
- user
237
- }])
399
+ async execute({ account }, { client, service }, emit) {
400
+ const accountData = await Account.get(account)
401
+ if(!accountData) throw 'notFound'
402
+ const { user } = accountData
403
+ if(user !== client.user) throw 'notAuthorized'
404
+ await service.trigger({
405
+ type: 'disconnectGoogle',
406
+ user, account
407
+ })
238
408
  }
239
409
  })
240
410
 
241
-
242
411
  export default definition
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-change/google-authentication-service",
3
- "version": "0.8.14",
3
+ "version": "0.8.15",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -21,10 +21,10 @@
21
21
  "url": "https://www.viamage.com/"
22
22
  },
23
23
  "dependencies": {
24
- "@live-change/framework": "^0.8.14",
25
- "@live-change/relations-plugin": "^0.8.14",
24
+ "@live-change/framework": "^0.8.15",
25
+ "@live-change/relations-plugin": "^0.8.15",
26
26
  "google-auth-library": "9.0.0"
27
27
  },
28
- "gitHead": "d81b573fb8891746ae1c67f4f68558123c9f85f7",
28
+ "gitHead": "4897eefbf3ce9ace57630127da89503c71114563",
29
29
  "type": "module"
30
30
  }