@live-change/linkedin-authentication-service 0.8.68

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/LICENSE.md ADDED
@@ -0,0 +1,11 @@
1
+ Copyright 2019-2020 Michał Łaszczewski
2
+
3
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4
+
5
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6
+
7
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+
9
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10
+
11
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md ADDED
@@ -0,0 +1 @@
1
+ # linkedin-login-service
package/account.js ADDED
@@ -0,0 +1,82 @@
1
+ import App from '@live-change/framework'
2
+ const app = App.app()
3
+ import definition from './definition.js'
4
+
5
+ export const User = definition.foreignModel("user", "User")
6
+
7
+ export const linkedinProperties = {
8
+ email: {
9
+ type: String
10
+ },
11
+ email_verified: {
12
+ type: Boolean
13
+ },
14
+ name: {
15
+ type: String
16
+ },
17
+ given_name: {
18
+ type: String
19
+ },
20
+ family_name: {
21
+ type: String
22
+ },
23
+ picture: {
24
+ type: String
25
+ },
26
+ locale: {
27
+ type: String
28
+ }
29
+ }
30
+
31
+ export const Account = definition.model({
32
+ name: "Account",
33
+ properties: {
34
+ ...linkedinProperties
35
+ },
36
+ userItem: {
37
+ userReadAccess: () => true
38
+ }
39
+ })
40
+
41
+ definition.event({
42
+ name: 'accountConnected',
43
+ properties: {
44
+ account: {
45
+ type: String,
46
+ validation: ['nonEmpty']
47
+ },
48
+ user: {
49
+ type: User,
50
+ validation: ['nonEmpty']
51
+ },
52
+ data: {
53
+ type: Object,
54
+ properties: linkedinProperties,
55
+ validation: ['nonEmpty']
56
+ }
57
+ },
58
+ async execute({ user, account, data }) {
59
+ await Account.create({
60
+ ...data,
61
+ id: account,
62
+ user
63
+ })
64
+ }
65
+ })
66
+
67
+ definition.event({
68
+ name: "accountDisconnected",
69
+ properties: {
70
+ account: {
71
+ type: String,
72
+ validation: ['nonEmpty']
73
+ },
74
+ user: {
75
+ type: User,
76
+ validation: ['nonEmpty']
77
+ }
78
+ },
79
+ async execute({ account }) {
80
+ await Account.delete(account)
81
+ }
82
+ })
package/connect.js ADDED
@@ -0,0 +1,149 @@
1
+ import App from '@live-change/framework'
2
+ const app = App.app()
3
+ import definition from './definition.js'
4
+
5
+ import Debug from 'debug'
6
+ const debug = Debug('services:linkedinAuthentication')
7
+
8
+ import { User, linkedinProperties, Account } from './account.js'
9
+ import { getTokensWithCode, getUserInfo } from './linkedinClient.js'
10
+
11
+ import { downloadData } from './downloadData.js'
12
+
13
+ definition.trigger({
14
+ name: "connectLinkedin",
15
+ properties: {
16
+ user: {
17
+ type: User,
18
+ validation: ['nonEmpty']
19
+ },
20
+ account: {
21
+ type: String,
22
+ validation: ['nonEmpty']
23
+ },
24
+ data: {
25
+ type: Object,
26
+ properties: linkedinProperties,
27
+ validation: ['nonEmpty']
28
+ },
29
+ transferOwnership: {
30
+ type: Boolean,
31
+ default: false
32
+ }
33
+ },
34
+ async execute({ user, account, data, transferOwnership },
35
+ context, emit) {
36
+ const { trigger } = context
37
+ const accountData = await Account.get(account)
38
+ if(accountData) {
39
+ if(accountData.user !== user) {
40
+ if(transferOwnership) {
41
+ emit({
42
+ 'type': 'userOwnedAccountTransferred',
43
+ account, to: user
44
+ })
45
+ await downloadData(user, data, context)
46
+ return
47
+ }
48
+ throw 'alreadyConnectedElsewhere'
49
+ }
50
+ throw 'alreadyConnected'
51
+ }
52
+ emit({
53
+ type: 'accountConnected',
54
+ account, user, data
55
+ })
56
+ await trigger({ type: 'linkedinConnected' }, {
57
+ user, account, data
58
+ })
59
+ await downloadData(user, data, context)
60
+ }
61
+ })
62
+
63
+ definition.trigger({
64
+ name: "disconnectLinkedin",
65
+ properties: {
66
+ account: {
67
+ type: String,
68
+ validation: ['nonEmpty']
69
+ }
70
+ },
71
+ async execute({ account }, { client, service }, emit) {
72
+ const accountData = await Account.get(account)
73
+ if(!accountData) throw 'notFound'
74
+ const { user } = accountData
75
+ emit({
76
+ type: 'accountDisconnected',
77
+ account, user
78
+ })
79
+ await service.trigger({ type: 'linkedinDisconnected' }, {
80
+ user, account
81
+ })
82
+ }
83
+ })
84
+
85
+
86
+ definition.action({
87
+ name: "connectLinkedin",
88
+ properties: {
89
+ code: {
90
+ type: String,
91
+ validation: ['nonEmpty']
92
+ },
93
+ redirectUri: {
94
+ type: String,
95
+ validation: ['nonEmpty']
96
+ },
97
+ transferOwnership: {
98
+ type: Boolean,
99
+ default: false
100
+ }
101
+ },
102
+ async execute({ accessToken, transferOwnership }, { client, service }, emit) {
103
+ const user = client.user
104
+ if(!user) throw 'notAuthorized'
105
+ const tokens = await getTokensWithCode(code, redirectUri)
106
+ debug("TOKENS", tokens)
107
+
108
+ const [linkedinUser, linkedinEmail] = await Promise.all([
109
+ getUserInfo(tokens.access_token),
110
+ getUserEmail(tokens.access_token)
111
+ ])
112
+
113
+ debug("LINKEDIN USER", linkedinUser)
114
+ debug("LINKEDIN EMAIL", linkedinEmail)
115
+
116
+ console.log("LINKEDIN USER", linkedinUser)
117
+ console.log("LINKEDIN EMAIL", linkedinEmail)
118
+ process.exit(1)
119
+
120
+ const account = linkedinUser.sub
121
+ await service.trigger({ type: 'connectLinkedin' }, {
122
+ user, account, data: linkedinUser,
123
+ transferOwnership
124
+ })
125
+ return {
126
+ action: 'connectLinkedin',
127
+ user
128
+ }
129
+ }
130
+ })
131
+
132
+ definition.action({
133
+ name: "disconnectLinkedin",
134
+ properties: {
135
+ account: {
136
+ type: String,
137
+ validation: ['nonEmpty']
138
+ }
139
+ },
140
+ async execute({ account }, { client, service }, emit) {
141
+ const accountData = await Account.get(account)
142
+ if(!accountData) throw 'notFound'
143
+ const { user } = accountData
144
+ if(user !== client.user) throw 'notAuthorized'
145
+ await service.trigger({ type: 'disconnectLinkedin' }, {
146
+ user, account
147
+ })
148
+ }
149
+ })
package/definition.js ADDED
@@ -0,0 +1,11 @@
1
+ import App from '@live-change/framework'
2
+ const app = App.app()
3
+
4
+ import user from '@live-change/user-service'
5
+
6
+ const definition = app.createServiceDefinition({
7
+ name: "linkedinAuthentication",
8
+ use: [ user ]
9
+ })
10
+
11
+ export default definition
@@ -0,0 +1,46 @@
1
+ import App from '@live-change/framework'
2
+ const app = App.app()
3
+ import definition from './definition.js'
4
+
5
+ export async function downloadData(user, data, { trigger, triggerService }) {
6
+ console.log("download data", { user, data })
7
+ const res = await trigger({ type: 'setIdentification' }, {
8
+ sessionOrUserType: 'user_User',
9
+ sessionOrUser: user,
10
+ overwrite: false,
11
+ name: data.name,
12
+ givenName: data.given_name,
13
+ firstName: data.given_name,
14
+ familyName: data.family_name,
15
+ lastName: data.family_name,
16
+ })
17
+ //console.log("IDENTIFICATION SET!", res)
18
+ if(data.picture) {
19
+ //console.log("WILL DOWNLOAD PICTURE", data.picture)
20
+ const downloadAndUpdateImage = (async () => {
21
+ //console.log("DOWNLOAD PICTURE", data.picture)
22
+ const image = await triggerService({ service: 'image', type: "createImageFromUrl" }, {
23
+ ownerType: 'user_User',
24
+ owner: user,
25
+ name: "linkedin-profile-picture",
26
+ purpose: "users-updatePicture-picture",
27
+ url: data.picture,
28
+ cropped: true
29
+ })
30
+ //console.log("IMAGE DOWNLOADED", picture)
31
+ await trigger({ type: 'setIdentification' }, {
32
+ sessionOrUserType: 'user_User',
33
+ sessionOrUser: user,
34
+ overwrite: false,
35
+ image
36
+ })
37
+ })
38
+ downloadAndUpdateImage()
39
+ }
40
+ if(data.email_verified) {
41
+ await trigger({ type: 'connectEmail' }, {
42
+ email: data.email,
43
+ user
44
+ })
45
+ }
46
+ }
package/index.js ADDED
@@ -0,0 +1,12 @@
1
+ import App from '@live-change/framework'
2
+ const app = App.app()
3
+ import definition from './definition.js'
4
+
5
+ import "./account.js"
6
+ import "./connect.js"
7
+ import "./sign.js"
8
+ import "./offlineAccess.js"
9
+
10
+
11
+
12
+ export default definition
@@ -0,0 +1,66 @@
1
+ import App from '@live-change/framework'
2
+ const app = App.app()
3
+ import definition from './definition.js'
4
+
5
+ import Debug from 'debug'
6
+ const debug = Debug('services:linkedinAuthentication')
7
+
8
+ import axios from 'axios'
9
+
10
+ const config = definition.config
11
+
12
+ const linkedinClientId = config.clientId || process.env.LINKEDIN_CLIENT_ID
13
+ const linkedinClientSecret = config.clientId || process.env.LINKEDIN_CLIENT_SECRET
14
+ export { linkedinClientId, linkedinClientSecret }
15
+
16
+
17
+ export async function getTokensWithCode(code, redirectUri) {
18
+ if (!code) throw new Error("No code provided")
19
+ if (!redirectUri) throw new Error("No redirectUri provided")
20
+
21
+ const params = new URLSearchParams()
22
+ params.append('grant_type', 'authorization_code')
23
+ params.append('code', code)
24
+ params.append('redirect_uri', redirectUri)
25
+ params.append('client_id', linkedinClientId)
26
+ params.append('client_secret', linkedinClientSecret)
27
+
28
+ const options = {
29
+ url: 'https://www.linkedin.com/oauth/v2/accessToken',
30
+ method: 'post',
31
+ headers: {
32
+ 'Content-Type': 'application/x-www-form-urlencoded',
33
+ },
34
+ data: params.toString(),
35
+ };
36
+
37
+ try {
38
+ const response = await axios(options)
39
+ return response.data
40
+ } catch (error) {
41
+ console.log("OAUTH REQUEST", options)
42
+ console.error("OAUTH ERROR", error?.stack || error?.message || JSON.stringify(error))
43
+ console.error("OAUTH ERROR RESPONSE", error?.response?.data)
44
+ throw error?.response?.data ? new Error(error?.response?.data) : error
45
+ }
46
+ }
47
+
48
+ export async function getUserInfo(accessToken) {
49
+ console.log("GET USER INFO", accessToken)
50
+ const options = {
51
+ url: 'https://api.linkedin.com/v2/userinfo',
52
+ method: 'get',
53
+ headers: {
54
+ Authorization: `Bearer ${accessToken}`
55
+ }
56
+ }
57
+
58
+ try {
59
+ const response = await axios(options)
60
+ return response.data
61
+ } catch(error) {
62
+ console.log("OAUTH REQUEST", options)
63
+ console.error("OAUTH ERROR", error)
64
+ throw error?.response?.data ? new Error(error?.response?.data) : error
65
+ }
66
+ }
@@ -0,0 +1,169 @@
1
+ import App from '@live-change/framework'
2
+ const app = App.app()
3
+ import definition from './definition.js'
4
+ import Debug from 'debug'
5
+ import { getTokensWithCode, getUserInfo } from './linkedinClient.js'
6
+ const debug = Debug('services:linkedinAuthentication')
7
+
8
+ export const OfflineAccess = definition.model({
9
+ name: "OfflineAccess",
10
+ userProperty: {
11
+ userReadAccess: () => true
12
+ },
13
+ properties: {
14
+ scopes: {
15
+ type: Array,
16
+ of: {
17
+ type: String,
18
+ validation: ['nonEmpty']
19
+ },
20
+ validation: ['nonEmpty']
21
+ },
22
+ refreshToken: {
23
+ type: String,
24
+ validation: ['nonEmpty']
25
+ },
26
+ accessToken: {
27
+ type: String
28
+ },
29
+ accessTokenExpire: {
30
+ type: Date
31
+ },
32
+ lastUse: {
33
+ type: Date
34
+ },
35
+ lastRefresh: {
36
+ type: Date
37
+ }
38
+ },
39
+ indexes: {
40
+ byUserAndScope: {
41
+ multi: true,
42
+ property: ['user', 'scopes']
43
+ }
44
+ }
45
+ })
46
+
47
+ definition.view({
48
+ name: 'myUserOfflineAccessByScope',
49
+ properties: {
50
+ scope: {
51
+ type: String
52
+ }
53
+ },
54
+ returns: {
55
+ type: OfflineAccess
56
+ },
57
+ access: (params, { client, service }) => {
58
+ return !!client.user
59
+ },
60
+ async daoPath({ scope }, { client }) {
61
+ return OfflineAccess.indexObjectPath('byUserAndScope', [client.user, scope])
62
+ }
63
+ })
64
+
65
+ definition.view({
66
+ name: 'userOfflineAccessByScope',
67
+ properties: {
68
+ user: {
69
+ type: String
70
+ },
71
+ scope: {
72
+ type: String
73
+ }
74
+ },
75
+ returns: {
76
+ type: OfflineAccess
77
+ },
78
+ internal: true,
79
+ async daoPath({ user, scope }, { client }) {
80
+ return OfflineAccess.indexObjectPath('byUserAndScope', [user, scope])
81
+ }
82
+ })
83
+
84
+ definition.event({
85
+ name: "offlineAccessSaved",
86
+ async execute({ user, refreshToken, timestamp }) {
87
+ await OfflineAccess.create({ id: user, user, refreshToken, lastUse: timestamp })
88
+ }
89
+ })
90
+
91
+ definition.event({
92
+ name: "offlineAccessDeleted",
93
+ async execute({ user }) {
94
+ await OfflineAccess.delete(user)
95
+ }
96
+ })
97
+
98
+ definition.event({
99
+ name: "offlineAccessUsed",
100
+ async execute({ user, timestamp }) {
101
+ await OfflineAccess.update(user, { lastUse: timestamp })
102
+ }
103
+ })
104
+
105
+ definition.event({
106
+ name: "offlineAccessRefreshed",
107
+ async execute({ user, timestamp }) {
108
+ await OfflineAccess.update(user, { lastRefresh: timestamp })
109
+ }
110
+ })
111
+
112
+ definition.event({
113
+ name: "offlineAccessDeleted",
114
+ async execute({ user }) {
115
+ await ApiAccess.delete(user)
116
+ }
117
+ })
118
+
119
+ definition.action({
120
+ name: 'addOfflineAccessToken',
121
+ properties: {
122
+ code: {
123
+ type: String,
124
+ validation: ['nonEmpty']
125
+ },
126
+ redirectUri: {
127
+ type: String,
128
+ validation: ['nonEmpty']
129
+ },
130
+ scope: {
131
+ type: String,
132
+ validation: ['nonEmpty']
133
+ }
134
+ },
135
+ async execute({ code, redirectUri, scope }, { client, service }, emit) {
136
+ const user = client.user
137
+ if(!user) throw 'notAuthorized'
138
+ const tokens = await getTokensWithCode(code, redirectUri)
139
+ console.log("TOKENS", tokens)
140
+ const scopes = tokens.scope.split(' ')
141
+ if(tokens.token_type !== 'Bearer') throw new Error("Invalid token type "+tokens.token_type)
142
+ await OfflineAccess.update(user, {
143
+ id: user,
144
+ user,
145
+ scopes,
146
+ refreshToken: tokens.refresh_token,
147
+ accessToken: tokens.access_token,
148
+ accessTokenExpire: tokens.expires_in ? new Date(Date.now() + tokens.expires_in * 1000) : null,
149
+ lastRefresh: new Date()
150
+ })
151
+ await service.triggerService({
152
+ type: 'linkedinAuthentication_setUserOwnedOfflineAccess', service: definition.name
153
+ }, {
154
+ user, scopes,
155
+ accessToken: tokens.access_token,
156
+ accessTokenExpire: tokens.expires_in ? new Date(Date.now() + tokens.expires_in * 1000) : null,
157
+ refreshToken: tokens.refresh_token,
158
+ })
159
+ await service.trigger({ type: 'linkedinOfflineAccessGained' }, {
160
+ user, scopes,
161
+ accessToken: tokens.access_token,
162
+ accessTokenExpire: tokens.expires_in ? new Date(Date.now() + tokens.expires_in * 1000) : null,
163
+ refreshToken: tokens.refresh_token,
164
+ })
165
+ return {
166
+ action: 'addOfflineAccessToken'
167
+ }
168
+ }
169
+ })
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@live-change/linkedin-authentication-service",
3
+ "version": "0.8.68",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "NODE_ENV=test tape tests/*"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/live-change/live-change-stack.git"
12
+ },
13
+ "license": "MIT",
14
+ "bugs": {
15
+ "url": "https://github.com/live-change/live-change-stack/issues"
16
+ },
17
+ "homepage": "https://github.com/live-change/live-change-stack",
18
+ "author": {
19
+ "email": "michal@laszczewski.pl",
20
+ "name": "Michał Łaszczewski",
21
+ "url": "https://www.viamage.com/"
22
+ },
23
+ "dependencies": {
24
+ "@live-change/framework": "^0.8.68",
25
+ "@live-change/relations-plugin": "^0.8.68"
26
+ },
27
+ "gitHead": "9f34ce84625f32092de0d93dcd19e4e4982982bd",
28
+ "type": "module"
29
+ }
package/sign.js ADDED
@@ -0,0 +1,160 @@
1
+ import App from '@live-change/framework'
2
+ const app = App.app()
3
+ import definition from './definition.js'
4
+
5
+ import Debug from 'debug'
6
+ const debug = Debug('services:linkedinAuthentication')
7
+
8
+ import { User, linkedinProperties, Account } from './account.js'
9
+ import { getTokensWithCode, linkedinClientId, getUserInfo } from './linkedinClient.js'
10
+
11
+ definition.trigger({
12
+ name: "signInLinkedin",
13
+ properties: {
14
+ account: {
15
+ type: String,
16
+ validation: ['nonEmpty']
17
+ }
18
+ },
19
+ async execute({ account, session }, { service }, _emit) {
20
+ const accountData = await Account.get(account)
21
+ if(!accountData) throw { properties: { email: 'notFound' } }
22
+ const { user } = accountData
23
+ return service.trigger({ type: 'signIn' }, {
24
+ user, session
25
+ })
26
+ }
27
+ })
28
+
29
+ definition.action({
30
+ name: "signIn",
31
+ properties: {
32
+ code: {
33
+ type: String,
34
+ validation: ['nonEmpty']
35
+ },
36
+ redirectUri: {
37
+ type: String,
38
+ validation: ['nonEmpty']
39
+ },
40
+ },
41
+ returns: {
42
+ type: User,
43
+ idOnly: true
44
+ },
45
+ waitForEvents: true,
46
+ async execute({ code, redirectUri }, { client, service }, emit) {
47
+ const tokens = await getTokensWithCode(code, redirectUri)
48
+ debug("TOKENS", tokens)
49
+ const linkedinUser = await getUserInfo(tokens.access_token)
50
+ debug("LINKEDIN USER", linkedinUser)
51
+ const account = linkedinUser.sub
52
+ const existingLogin = await Account.get(account)
53
+ const { session } = client
54
+ if(existingLogin) { /// Sign In
55
+ const { user } = existingLogin
56
+ await service.trigger({ type: 'signIn' }, {
57
+ user, session
58
+ })
59
+ return {
60
+ action: 'signIn',
61
+ user
62
+ }
63
+ } else { // Sign up
64
+ throw 'notFound'
65
+ }
66
+ }
67
+ })
68
+
69
+ definition.action({
70
+ name: "signUp",
71
+ properties: {
72
+ code: {
73
+ type: String,
74
+ validation: ['nonEmpty']
75
+ },
76
+ redirectUri: {
77
+ type: String,
78
+ validation: ['nonEmpty']
79
+ },
80
+ },
81
+ returns: {
82
+ type: User,
83
+ idOnly: true
84
+ },
85
+ waitForEvents: true,
86
+ async execute({ accessToken }, { client, service }, emit) {
87
+ const tokens = await getTokensWithCode(code, redirectUri)
88
+ debug("TOKENS", tokens)
89
+ const linkedinUser = await getUserInfo(tokens.access_token)
90
+ debug("LINKEDIN USER", linkedinUser)
91
+ const account = linkedinUser.sub
92
+ const existingLogin = await Account.get(account)
93
+ const { session } = client
94
+ if(existingLogin) { /// Sign In
95
+ throw 'alreadyConnected'
96
+ } else { // Sign up
97
+ const user = app.generateUid()
98
+ await service.trigger({ type: 'connectLinkedin' }, {
99
+ user, account, data: linkedinUser
100
+ })
101
+ await service.trigger({ type: 'signUpAndSignIn' }, {
102
+ user, session
103
+ })
104
+ return {
105
+ action: 'signUp',
106
+ user
107
+ }
108
+ }
109
+ }
110
+ })
111
+
112
+ definition.action({
113
+ name: "signInOrSignUp",
114
+ properties: {
115
+ code: {
116
+ type: String,
117
+ validation: ['nonEmpty']
118
+ },
119
+ redirectUri: {
120
+ type: String,
121
+ validation: ['nonEmpty']
122
+ },
123
+ },
124
+ returns: {
125
+ type: User,
126
+ idOnly: true
127
+ },
128
+ waitForEvents: true,
129
+ async execute({ code, redirectUri }, { client, service }, emit) {
130
+ const tokens = await getTokensWithCode(code, redirectUri)
131
+ debug("TOKENS", tokens)
132
+ const linkedinUser = await getUserInfo(tokens.access_token)
133
+ debug("LINKEDIN USER", linkedinUser)
134
+ const account = linkedinUser.sub
135
+ const existingLogin = await Account.get(account)
136
+ const { session } = client
137
+ if(existingLogin) { /// Sign In
138
+ const { user } = existingLogin
139
+ await service.trigger({ type: 'signIn' }, {
140
+ user, session
141
+ })
142
+ return {
143
+ action: 'signIn',
144
+ user: existingLogin.user
145
+ }
146
+ } else { // Sign up
147
+ const user = app.generateUid()
148
+ await service.trigger({ type: 'connectLinkedin' }, {
149
+ user, account, data: linkedinUser
150
+ })
151
+ await service.trigger({ type: 'signUpAndSignIn' }, {
152
+ user, session
153
+ })
154
+ return {
155
+ action: 'signUp',
156
+ user
157
+ }
158
+ }
159
+ }
160
+ })
package/tests/login.js ADDED
@@ -0,0 +1,22 @@
1
+ const test = require('blue-tape')
2
+ const r = require('rethinkdb')
3
+ const testUtils = require('rethink-event-sourcing/tape-test-utils.js')
4
+ const crypto = require('crypto')
5
+
6
+ test('Facebook register, login, disconnect', t => {
7
+
8
+ t.plan(2)
9
+
10
+ let conn
11
+ testUtils.connectToDatabase(t, r, (connection) => conn = connection)
12
+
13
+ /// TODO: mock Linkedin somehow ?!
14
+
15
+ t.test('close connection', t => {
16
+ conn.close(() => {
17
+ t.pass('closed')
18
+ t.end()
19
+ })
20
+ })
21
+
22
+ })