@live-change/password-authentication-service 0.2.12 → 0.2.13
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/change.js +47 -0
- package/definition.js +9 -0
- package/index.js +5 -137
- package/model.js +34 -0
- package/package.json +2 -2
- package/reset.js +158 -0
- package/signIn.js +62 -0
package/change.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const definition = require('./definition.js')
|
|
2
|
+
const config = definition.config
|
|
3
|
+
const { PasswordAuthentication, secretProperties } = require('./model.js')
|
|
4
|
+
|
|
5
|
+
definition.action({
|
|
6
|
+
name: 'setPassword',
|
|
7
|
+
waitForEvents: true,
|
|
8
|
+
properties: {
|
|
9
|
+
...secretProperties
|
|
10
|
+
},
|
|
11
|
+
access: (params, { client }) => {
|
|
12
|
+
return !!client.user
|
|
13
|
+
},
|
|
14
|
+
async execute({ passwordHash }, { client, service }, emit) {
|
|
15
|
+
const user = client.user
|
|
16
|
+
const passwordAuthenticationData = await PasswordAuthentication.get(user)
|
|
17
|
+
if(passwordAuthenticationData) throw 'exists'
|
|
18
|
+
emit({
|
|
19
|
+
type: 'passwordAuthenticationSet',
|
|
20
|
+
user, passwordHash
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
definition.action({
|
|
26
|
+
name: 'changePassword',
|
|
27
|
+
waitForEvents: true,
|
|
28
|
+
properties: {
|
|
29
|
+
currentPasswordHash: secretProperties.passwordHash,
|
|
30
|
+
...secretProperties
|
|
31
|
+
},
|
|
32
|
+
access: (params, { client }) => {
|
|
33
|
+
return !!client.user
|
|
34
|
+
},
|
|
35
|
+
async execute({ currentPasswordHash, passwordHash }, { client, service }, emit) {
|
|
36
|
+
const user = client.user
|
|
37
|
+
const passwordAuthenticationData = await PasswordAuthentication.get(user)
|
|
38
|
+
if(!passwordAuthenticationData) throw 'notFound'
|
|
39
|
+
if(currentPasswordHash != passwordAuthenticationData.passwordHash) throw { properties: {
|
|
40
|
+
currentPasswordHash: 'wrongPassword'
|
|
41
|
+
} }
|
|
42
|
+
emit({
|
|
43
|
+
type: 'passwordAuthenticationSet',
|
|
44
|
+
user, passwordHash
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
})
|
package/definition.js
ADDED
package/index.js
CHANGED
|
@@ -1,141 +1,9 @@
|
|
|
1
|
-
const
|
|
2
|
-
const app = require("@live-change/framework").app()
|
|
3
|
-
const user = require('@live-change/user-service')
|
|
4
|
-
const { AuthenticatedUser } = require("../user-service/model.js")
|
|
1
|
+
const definition = require('./definition.js')
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const config = definition.config
|
|
11
|
-
|
|
12
|
-
const User = definition.foreignModel('user', 'User')
|
|
13
|
-
|
|
14
|
-
const secretProperties = {
|
|
15
|
-
passwordHash: {
|
|
16
|
-
type: String,
|
|
17
|
-
secret: true,
|
|
18
|
-
preFilter: config.passwordHash ||
|
|
19
|
-
(password => password && crypto.createHash('sha256').update(password).digest('hex')),
|
|
20
|
-
validation: config.passwordValidation || ['password']
|
|
21
|
-
},
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const PasswordAuthentication = definition.model({
|
|
25
|
-
name: 'PasswordAuthentication',
|
|
26
|
-
userProperty: {
|
|
27
|
-
userViews: [
|
|
28
|
-
{ suffix: 'Exists', fields: ['user'] }
|
|
29
|
-
]
|
|
30
|
-
},
|
|
31
|
-
properties: {
|
|
32
|
-
...secretProperties,
|
|
33
|
-
}
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
definition.event({
|
|
37
|
-
name: 'passwordAuthenticationSet',
|
|
38
|
-
async execute({ user, passwordHash }) {
|
|
39
|
-
await PasswordAuthentication.create({ id: user, user, passwordHash })
|
|
40
|
-
}
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
definition.action({
|
|
44
|
-
name: 'setPassword',
|
|
45
|
-
waitForEvents: true,
|
|
46
|
-
properties: {
|
|
47
|
-
...secretProperties
|
|
48
|
-
},
|
|
49
|
-
access: (params, { client }) => {
|
|
50
|
-
return !!client.user
|
|
51
|
-
},
|
|
52
|
-
async execute({ passwordHash }, { client, service }, emit) {
|
|
53
|
-
const user = client.user
|
|
54
|
-
const passwordAuthenticationData = await PasswordAuthentication.get(user)
|
|
55
|
-
if(passwordAuthenticationData) throw 'exists'
|
|
56
|
-
emit({
|
|
57
|
-
type: 'passwordAuthenticationSet',
|
|
58
|
-
user, passwordHash
|
|
59
|
-
})
|
|
60
|
-
}
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
definition.action({
|
|
64
|
-
name: 'changePassword',
|
|
65
|
-
waitForEvents: true,
|
|
66
|
-
properties: {
|
|
67
|
-
currentPasswordHash: secretProperties.passwordHash,
|
|
68
|
-
...secretProperties
|
|
69
|
-
},
|
|
70
|
-
access: (params, { client }) => {
|
|
71
|
-
return !!client.user
|
|
72
|
-
},
|
|
73
|
-
async execute({ currentPasswordHash, passwordHash }, { client, service }, emit) {
|
|
74
|
-
const user = client.user
|
|
75
|
-
const passwordAuthenticationData = await PasswordAuthentication.get(user)
|
|
76
|
-
if(!passwordAuthenticationData) throw 'notFound'
|
|
77
|
-
if(currentPasswordHash != passwordAuthenticationData.passwordHash) throw { properties: {
|
|
78
|
-
currentPasswordHash: 'wrongPassword'
|
|
79
|
-
} }
|
|
80
|
-
emit({
|
|
81
|
-
type: 'passwordAuthenticationSet',
|
|
82
|
-
user, passwordHash
|
|
83
|
-
})
|
|
84
|
-
}
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
for(const contactType of config.contactTypes) {
|
|
88
|
-
const contactTypeUpperCaseName = contactType[0].toUpperCase() + contactType.slice(1)
|
|
89
|
-
|
|
90
|
-
const contactConfig = (typeof contactType == "string") ? { name: contactType } : contactType
|
|
91
|
-
|
|
92
|
-
const contactTypeName = contactConfig.name
|
|
93
|
-
const contactTypeUName = contactTypeName[0].toUpperCase() + contactTypeName.slice(1)
|
|
94
|
-
|
|
95
|
-
const contactTypeProperties = {
|
|
96
|
-
[contactType]: {
|
|
97
|
-
type: String,
|
|
98
|
-
validation: ['nonEmpty', contactTypeName]
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
definition.action({
|
|
103
|
-
name: 'signIn' + contactTypeUpperCaseName,
|
|
104
|
-
waitForEvents: true,
|
|
105
|
-
properties: {
|
|
106
|
-
...contactTypeProperties,
|
|
107
|
-
...secretProperties
|
|
108
|
-
},
|
|
109
|
-
async execute({ [contactTypeName]: contact, passwordHash }, { client, service }, emit) {
|
|
110
|
-
await service.trigger({
|
|
111
|
-
type: 'checkExisting' + contactTypeUName,
|
|
112
|
-
[contactType]: contact,
|
|
113
|
-
})
|
|
114
|
-
if(passwordHash) { // login with password
|
|
115
|
-
throw Error('not implemented yet')
|
|
116
|
-
} else { // login without password - with message
|
|
117
|
-
if(!config.signInWithoutPassword) throw { properties: { passwordHash: 'empty' } }
|
|
118
|
-
const contactData = (await service.trigger({
|
|
119
|
-
type: 'get' + contactTypeUName,
|
|
120
|
-
[contactType]: contact,
|
|
121
|
-
}))[0]
|
|
122
|
-
const messageData = {
|
|
123
|
-
user: contactData.user
|
|
124
|
-
}
|
|
125
|
-
const results = await service.trigger({
|
|
126
|
-
type: 'authenticateWithMessage',
|
|
127
|
-
contactType,
|
|
128
|
-
contact,
|
|
129
|
-
messageData,
|
|
130
|
-
action: 'signInWithMessage',
|
|
131
|
-
targetPage: config.signInTargetPage || { name: 'user:signInFinished' }
|
|
132
|
-
})
|
|
133
|
-
return results[0]
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
}
|
|
3
|
+
require('./model.js')
|
|
4
|
+
require('./change.js')
|
|
5
|
+
require('./signIn.js')
|
|
6
|
+
require('./reset.js')
|
|
139
7
|
|
|
140
8
|
definition.processor(function(service, app) {
|
|
141
9
|
service.validators.password = require('./passwordValidator.js')
|
package/model.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const crypto = require('crypto')
|
|
2
|
+
const definition = require('./definition.js')
|
|
3
|
+
const config = definition.config
|
|
4
|
+
|
|
5
|
+
const secretProperties = {
|
|
6
|
+
passwordHash: {
|
|
7
|
+
type: String,
|
|
8
|
+
secret: true,
|
|
9
|
+
preFilter: config.passwordHash ||
|
|
10
|
+
(password => password && crypto.createHash('sha256').update(password).digest('hex')),
|
|
11
|
+
validation: config.passwordValidation || ['password']
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const PasswordAuthentication = definition.model({
|
|
16
|
+
name: 'PasswordAuthentication',
|
|
17
|
+
userProperty: {
|
|
18
|
+
userViews: [
|
|
19
|
+
{ suffix: 'Exists', fields: ['user'] }
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
properties: {
|
|
23
|
+
...secretProperties,
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
definition.event({
|
|
28
|
+
name: 'passwordAuthenticationSet',
|
|
29
|
+
async execute({ user, passwordHash }) {
|
|
30
|
+
await PasswordAuthentication.create({ id: user, user, passwordHash })
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
module.exports = { PasswordAuthentication, secretProperties }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-change/password-authentication-service",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.13",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -24,5 +24,5 @@
|
|
|
24
24
|
"@live-change/framework": "^0.5.7",
|
|
25
25
|
"nodemailer": "^6.7.2"
|
|
26
26
|
},
|
|
27
|
-
"gitHead": "
|
|
27
|
+
"gitHead": "add122f25ae8a848c2d24e1a75c3c9ae69d86faa"
|
|
28
28
|
}
|
package/reset.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
const app = require("@live-change/framework").app()
|
|
2
|
+
const { randomString } = require('@live-change/uid')
|
|
3
|
+
const definition = require('./definition.js')
|
|
4
|
+
const config = definition.config
|
|
5
|
+
const { PasswordAuthentication, secretProperties } = require('./model.js')
|
|
6
|
+
|
|
7
|
+
const User = definition.foreignModel('user', 'User')
|
|
8
|
+
|
|
9
|
+
const ResetPasswordAuthentication = definition.model({
|
|
10
|
+
name: 'ResetPasswordAuthentication',
|
|
11
|
+
userProperty: {
|
|
12
|
+
userReadAccess: () => true
|
|
13
|
+
},
|
|
14
|
+
properties: {
|
|
15
|
+
expire: {
|
|
16
|
+
type: Date,
|
|
17
|
+
validation: ['nonEmpty']
|
|
18
|
+
},
|
|
19
|
+
key: {
|
|
20
|
+
type: String,
|
|
21
|
+
validation: ['nonEmpty']
|
|
22
|
+
},
|
|
23
|
+
state: {
|
|
24
|
+
type: String,
|
|
25
|
+
validation: ['nonEmpty']
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
indexes: {
|
|
29
|
+
byKey: {
|
|
30
|
+
property: 'key'
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
definition.event({
|
|
36
|
+
name: 'resetPasswordAuthenticationCreated',
|
|
37
|
+
async execute({ resetPasswordAuthentication, user, key, expire }) {
|
|
38
|
+
await ResetPasswordAuthentication.create({ id: resetPasswordAuthentication, user, key, expire, state: 'created' })
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
definition.event({
|
|
43
|
+
name: 'resetPasswordAuthenticationUsed',
|
|
44
|
+
async execute({ resetPasswordAuthentication }) {
|
|
45
|
+
await ResetPasswordAuthentication.update(resetPasswordAuthentication, { state: 'used' })
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
definition.view({
|
|
50
|
+
name: "resetPasswordAuthentication",
|
|
51
|
+
properties: {
|
|
52
|
+
key: {
|
|
53
|
+
type: String,
|
|
54
|
+
validation: ['nonEmpty']
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
daoPath({ key }, { client, context }) {
|
|
58
|
+
return ResetPasswordAuthentication.indexObjectPath('byKey', key)
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
for(const contactType of config.contactTypes) {
|
|
63
|
+
|
|
64
|
+
const contactConfig = (typeof contactType == "string") ? { name: contactType } : contactType
|
|
65
|
+
const contactTypeName = contactConfig.name
|
|
66
|
+
const contactTypeUName = contactTypeName[0].toUpperCase() + contactTypeName.slice(1)
|
|
67
|
+
|
|
68
|
+
const contactTypeProperties = {
|
|
69
|
+
[contactTypeName]: {
|
|
70
|
+
type: String,
|
|
71
|
+
validation: ['nonEmpty', contactTypeName]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
definition.action({
|
|
76
|
+
name: 'resetPassword' + contactTypeUName,
|
|
77
|
+
waitForEvents: true,
|
|
78
|
+
properties: {
|
|
79
|
+
...contactTypeProperties
|
|
80
|
+
},
|
|
81
|
+
async execute({ [contactTypeName]: contact }, { client, service }, emit) {
|
|
82
|
+
const contactData = (await service.trigger({
|
|
83
|
+
type: 'get' + contactTypeUName,
|
|
84
|
+
[contactTypeName]: contact,
|
|
85
|
+
}))[0]
|
|
86
|
+
const { user } = contactData
|
|
87
|
+
const messageData = { user }
|
|
88
|
+
const actionProperties = { user }
|
|
89
|
+
return (await service.trigger({
|
|
90
|
+
type: 'authenticateWithMessage',
|
|
91
|
+
contactType,
|
|
92
|
+
contact,
|
|
93
|
+
messageData,
|
|
94
|
+
action: 'startResetPasswordWithMessage',
|
|
95
|
+
actionProperties,
|
|
96
|
+
targetPage: config.resetPasswordTargetPage || { name: 'user:resetPasswordForm' }
|
|
97
|
+
}))[0]
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
definition.trigger({
|
|
104
|
+
name: 'startResetPasswordWithMessageAuthenticated',
|
|
105
|
+
waitForEvents: true,
|
|
106
|
+
properties: {
|
|
107
|
+
user: {
|
|
108
|
+
type: User,
|
|
109
|
+
validation: ['nonEmpty']
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
async execute({ user, session }, { client, service }, emit) {
|
|
113
|
+
if(!user) throw new Error('user required')
|
|
114
|
+
const resetPasswordAuthentication = app.generateUid()
|
|
115
|
+
const key = randomString(config.resetKeyLength || 16)
|
|
116
|
+
const expire = new Date()
|
|
117
|
+
expire.setTime(Date.now() + (config.resetExpireTime || 1*60*60*1000))
|
|
118
|
+
service.trigger({
|
|
119
|
+
type: 'signIn',
|
|
120
|
+
user, session
|
|
121
|
+
})
|
|
122
|
+
emit({
|
|
123
|
+
type: 'resetPasswordAuthenticationCreated',
|
|
124
|
+
resetPasswordAuthentication,
|
|
125
|
+
user, key, expire
|
|
126
|
+
})
|
|
127
|
+
return {
|
|
128
|
+
resetKey: key
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
definition.action({
|
|
134
|
+
name: 'finishResetPassword',
|
|
135
|
+
waitForEvents: true,
|
|
136
|
+
properties: {
|
|
137
|
+
key: {
|
|
138
|
+
type: String,
|
|
139
|
+
validation: ['nonEmpty']
|
|
140
|
+
},
|
|
141
|
+
...secretProperties
|
|
142
|
+
},
|
|
143
|
+
async execute({ key, passwordHash }, { client, service }, emit) {
|
|
144
|
+
const resetPasswordAuthenticationData = await ResetPasswordAuthentication.indexObjectGet('byKey', key)
|
|
145
|
+
console.log("RESET AUTH", resetPasswordAuthenticationData)
|
|
146
|
+
if(!resetPasswordAuthenticationData) throw 'authenticationNotFound'
|
|
147
|
+
if(resetPasswordAuthenticationData.state == 'used') throw 'authenticationUsed'
|
|
148
|
+
if(resetPasswordAuthenticationData.expire < (new Date().toISOString())) throw 'authenticationExpired'
|
|
149
|
+
const { user } = resetPasswordAuthenticationData
|
|
150
|
+
emit([{
|
|
151
|
+
type: 'passwordAuthenticationSet',
|
|
152
|
+
user, passwordHash
|
|
153
|
+
}, {
|
|
154
|
+
type: 'resetPasswordAuthenticationUsed',
|
|
155
|
+
resetPasswordAuthentication: resetPasswordAuthenticationData.id
|
|
156
|
+
}])
|
|
157
|
+
}
|
|
158
|
+
})
|
package/signIn.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const definition = require('./definition.js')
|
|
2
|
+
const config = definition.config
|
|
3
|
+
|
|
4
|
+
const { PasswordAuthentication, secretProperties } = require('./model.js')
|
|
5
|
+
|
|
6
|
+
for(const contactType of config.contactTypes) {
|
|
7
|
+
const contactTypeUpperCaseName = contactType[0].toUpperCase() + contactType.slice(1)
|
|
8
|
+
|
|
9
|
+
const contactConfig = (typeof contactType == "string") ? { name: contactType } : contactType
|
|
10
|
+
|
|
11
|
+
const contactTypeName = contactConfig.name
|
|
12
|
+
const contactTypeUName = contactTypeName[0].toUpperCase() + contactTypeName.slice(1)
|
|
13
|
+
|
|
14
|
+
const contactTypeProperties = {
|
|
15
|
+
[contactType]: {
|
|
16
|
+
type: String,
|
|
17
|
+
validation: ['nonEmpty', contactTypeName]
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
definition.action({
|
|
22
|
+
name: 'signIn' + contactTypeUpperCaseName,
|
|
23
|
+
waitForEvents: true,
|
|
24
|
+
properties: {
|
|
25
|
+
...contactTypeProperties,
|
|
26
|
+
...secretProperties
|
|
27
|
+
},
|
|
28
|
+
async execute({ [contactTypeName]: contact, passwordHash }, { client, service }, emit) {
|
|
29
|
+
const contactData = (await service.trigger({
|
|
30
|
+
type: 'get' + contactTypeUName,
|
|
31
|
+
[contactType]: contact,
|
|
32
|
+
}))[0]
|
|
33
|
+
const { user } = contactData
|
|
34
|
+
if(passwordHash) { // login with password
|
|
35
|
+
console.log("USER!", user)
|
|
36
|
+
const passwordAuthenticationData = await PasswordAuthentication.get(user)
|
|
37
|
+
console.log("PASSWORD AUTH!", passwordAuthenticationData)
|
|
38
|
+
if(!passwordAuthenticationData || passwordAuthenticationData.passwordHash != passwordHash)
|
|
39
|
+
throw { properties: { passwordHash: 'wrongPassword' } }
|
|
40
|
+
await service.trigger({
|
|
41
|
+
type: 'signIn', user, session
|
|
42
|
+
})
|
|
43
|
+
return user
|
|
44
|
+
} else { // login without password - with message
|
|
45
|
+
if(!config.signInWithoutPassword) throw { properties: { passwordHash: 'empty' } }
|
|
46
|
+
const messageData = {
|
|
47
|
+
user
|
|
48
|
+
}
|
|
49
|
+
const results = await service.trigger({
|
|
50
|
+
type: 'authenticateWithMessage',
|
|
51
|
+
contactType,
|
|
52
|
+
contact,
|
|
53
|
+
messageData,
|
|
54
|
+
action: 'signInWithMessage',
|
|
55
|
+
targetPage: config.signInTargetPage || { name: 'user:signInFinished' }
|
|
56
|
+
})
|
|
57
|
+
return results[0]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
}
|