@feathersjs/authentication-local 5.0.0-pre.6 → 5.0.0
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/CHANGELOG.md +170 -198
- package/LICENSE +1 -1
- package/README.md +2 -2
- package/lib/hooks/hash-password.d.ts +8 -1
- package/lib/hooks/hash-password.js +19 -20
- package/lib/hooks/hash-password.js.map +1 -1
- package/lib/hooks/protect.d.ts +5 -1
- package/lib/hooks/protect.js +28 -32
- package/lib/hooks/protect.js.map +1 -1
- package/lib/index.d.ts +15 -2
- package/lib/index.js +20 -3
- package/lib/index.js.map +1 -1
- package/lib/strategy.js +78 -79
- package/lib/strategy.js.map +1 -1
- package/package.json +20 -18
- package/src/hooks/hash-password.ts +36 -28
- package/src/hooks/protect.ts +30 -25
- package/src/index.ts +27 -4
- package/src/strategy.ts +70 -62
|
@@ -1,59 +1,67 @@
|
|
|
1
|
-
import get from 'lodash/get'
|
|
2
|
-
import set from 'lodash/set'
|
|
3
|
-
import cloneDeep from 'lodash/cloneDeep'
|
|
4
|
-
import { BadRequest } from '@feathersjs/errors'
|
|
5
|
-
import { createDebug } from '@feathersjs/commons'
|
|
6
|
-
import { HookContext, NextFunction } from '@feathersjs/feathers'
|
|
7
|
-
import { LocalStrategy } from '../strategy'
|
|
1
|
+
import get from 'lodash/get'
|
|
2
|
+
import set from 'lodash/set'
|
|
3
|
+
import cloneDeep from 'lodash/cloneDeep'
|
|
4
|
+
import { BadRequest } from '@feathersjs/errors'
|
|
5
|
+
import { createDebug } from '@feathersjs/commons'
|
|
6
|
+
import { HookContext, NextFunction } from '@feathersjs/feathers'
|
|
7
|
+
import { LocalStrategy } from '../strategy'
|
|
8
8
|
|
|
9
|
-
const debug = createDebug('@feathersjs/authentication-local/hooks/hash-password')
|
|
9
|
+
const debug = createDebug('@feathersjs/authentication-local/hooks/hash-password')
|
|
10
10
|
|
|
11
11
|
export interface HashPasswordOptions {
|
|
12
|
-
authentication?: string
|
|
13
|
-
strategy?: string
|
|
12
|
+
authentication?: string
|
|
13
|
+
strategy?: string
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* @deprecated Use Feathers schema resolvers and the `passwordHash` resolver instead
|
|
18
|
+
* @param field
|
|
19
|
+
* @param options
|
|
20
|
+
* @returns
|
|
21
|
+
* @see https://dove.feathersjs.com/api/authentication/local.html#passwordhash
|
|
22
|
+
*/
|
|
23
|
+
export default function hashPassword(field: string, options: HashPasswordOptions = {}) {
|
|
17
24
|
if (!field) {
|
|
18
|
-
throw new Error('The hashPassword hook requires a field name option')
|
|
25
|
+
throw new Error('The hashPassword hook requires a field name option')
|
|
19
26
|
}
|
|
20
27
|
|
|
21
|
-
return async (context: HookContext
|
|
22
|
-
const { app, data, params } = context
|
|
28
|
+
return async (context: HookContext, next?: NextFunction) => {
|
|
29
|
+
const { app, data, params } = context
|
|
23
30
|
|
|
24
31
|
if (data !== undefined) {
|
|
25
|
-
const authService = app.defaultAuthentication(options.authentication)
|
|
26
|
-
const { strategy = 'local' } = options
|
|
32
|
+
const authService = app.defaultAuthentication(options.authentication)
|
|
33
|
+
const { strategy = 'local' } = options
|
|
27
34
|
|
|
28
35
|
if (!authService || typeof authService.getStrategies !== 'function') {
|
|
29
|
-
throw new BadRequest('Could not find an authentication service to hash password')
|
|
36
|
+
throw new BadRequest('Could not find an authentication service to hash password')
|
|
30
37
|
}
|
|
31
38
|
|
|
32
|
-
const [
|
|
39
|
+
const [localStrategy] = authService.getStrategies(strategy) as LocalStrategy[]
|
|
33
40
|
|
|
34
41
|
if (!localStrategy || typeof localStrategy.hashPassword !== 'function') {
|
|
35
|
-
throw new BadRequest(`Could not find '${strategy}' strategy to hash password`)
|
|
42
|
+
throw new BadRequest(`Could not find '${strategy}' strategy to hash password`)
|
|
36
43
|
}
|
|
37
44
|
|
|
38
45
|
const addHashedPassword = async (data: any) => {
|
|
39
|
-
const password = get(data, field)
|
|
46
|
+
const password = get(data, field)
|
|
40
47
|
|
|
41
48
|
if (password === undefined) {
|
|
42
|
-
debug(`hook.data.${field} is undefined, not hashing password`)
|
|
43
|
-
return data
|
|
49
|
+
debug(`hook.data.${field} is undefined, not hashing password`)
|
|
50
|
+
return data
|
|
44
51
|
}
|
|
45
52
|
|
|
46
|
-
const hashedPassword: string = await localStrategy.hashPassword(password, params)
|
|
53
|
+
const hashedPassword: string = await localStrategy.hashPassword(password, params)
|
|
47
54
|
|
|
48
|
-
return set(cloneDeep(data), field, hashedPassword)
|
|
55
|
+
return set(cloneDeep(data), field, hashedPassword)
|
|
49
56
|
}
|
|
50
57
|
|
|
51
|
-
context.data = Array.isArray(data)
|
|
52
|
-
await
|
|
58
|
+
context.data = Array.isArray(data)
|
|
59
|
+
? await Promise.all(data.map(addHashedPassword))
|
|
60
|
+
: await addHashedPassword(data)
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
if (typeof next === 'function') {
|
|
56
|
-
|
|
64
|
+
return next()
|
|
57
65
|
}
|
|
58
|
-
}
|
|
66
|
+
}
|
|
59
67
|
}
|
package/src/hooks/protect.ts
CHANGED
|
@@ -1,37 +1,42 @@
|
|
|
1
|
-
import omit from 'lodash/omit'
|
|
2
|
-
import { HookContext, NextFunction } from '@feathersjs/feathers'
|
|
1
|
+
import omit from 'lodash/omit'
|
|
2
|
+
import { HookContext, NextFunction } from '@feathersjs/feathers'
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* @deprecated For reliable safe data representations use Feathers schema dispatch resolvers.
|
|
6
|
+
* @see https://dove.feathersjs.comapi/authentication/local.html#protecting-fields
|
|
7
|
+
*/
|
|
8
|
+
export default (...fields: string[]) => {
|
|
5
9
|
const o = (current: any) => {
|
|
6
10
|
if (typeof current === 'object' && !Array.isArray(current)) {
|
|
7
|
-
const data = typeof current.toJSON === 'function'
|
|
8
|
-
? current.toJSON() : current;
|
|
11
|
+
const data = typeof current.toJSON === 'function' ? current.toJSON() : current
|
|
9
12
|
|
|
10
|
-
return omit(data, fields)
|
|
13
|
+
return omit(data, fields)
|
|
11
14
|
}
|
|
12
15
|
|
|
13
|
-
return current
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
if (typeof next === 'function') {
|
|
17
|
-
await next();
|
|
16
|
+
return current
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (Array.isArray(result)) {
|
|
24
|
-
context.dispatch = result.map(o);
|
|
25
|
-
} else if (result.data && context.method === 'find') {
|
|
26
|
-
context.dispatch = Object.assign({}, result, {
|
|
27
|
-
data: result.data.map(o)
|
|
28
|
-
});
|
|
29
|
-
} else {
|
|
30
|
-
context.dispatch = o(result);
|
|
19
|
+
return async (context: HookContext, next?: NextFunction) => {
|
|
20
|
+
if (typeof next === 'function') {
|
|
21
|
+
await next()
|
|
31
22
|
}
|
|
32
23
|
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
const result = context.dispatch || context.result
|
|
25
|
+
|
|
26
|
+
if (result) {
|
|
27
|
+
if (Array.isArray(result)) {
|
|
28
|
+
context.dispatch = result.map(o)
|
|
29
|
+
} else if (result.data && context.method === 'find') {
|
|
30
|
+
context.dispatch = Object.assign({}, result, {
|
|
31
|
+
data: result.data.map(o)
|
|
32
|
+
})
|
|
33
|
+
} else {
|
|
34
|
+
context.dispatch = o(result)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (context.params && context.params.provider) {
|
|
38
|
+
context.result = context.dispatch
|
|
39
|
+
}
|
|
35
40
|
}
|
|
36
41
|
}
|
|
37
|
-
}
|
|
42
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { HookContext } from '@feathersjs/feathers'
|
|
2
|
+
import hashPassword from './hooks/hash-password'
|
|
3
|
+
import protect from './hooks/protect'
|
|
4
|
+
import { LocalStrategy } from './strategy'
|
|
3
5
|
|
|
4
|
-
export const hooks = { hashPassword, protect }
|
|
5
|
-
export { LocalStrategy }
|
|
6
|
+
export const hooks = { hashPassword, protect }
|
|
7
|
+
export { LocalStrategy }
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Returns as property resolver that hashes a given plain text password using a Local
|
|
11
|
+
* authentication strategy.
|
|
12
|
+
*
|
|
13
|
+
* @param options The authentication `service` and `strategy` name
|
|
14
|
+
* @returns
|
|
15
|
+
*/
|
|
16
|
+
export const passwordHash =
|
|
17
|
+
(options: { service?: string; strategy: string }) =>
|
|
18
|
+
async <H extends HookContext<any, any>>(value: string | undefined, _data: any, context: H) => {
|
|
19
|
+
if (value === undefined) {
|
|
20
|
+
return value
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { app, params } = context
|
|
24
|
+
const authService = app.defaultAuthentication(options.service)
|
|
25
|
+
const localStrategy = authService.getStrategy(options.strategy) as LocalStrategy
|
|
26
|
+
|
|
27
|
+
return localStrategy.hashPassword(value, params)
|
|
28
|
+
}
|
package/src/strategy.ts
CHANGED
|
@@ -1,30 +1,28 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
-
import bcrypt from 'bcryptjs'
|
|
3
|
-
import get from 'lodash/get'
|
|
4
|
-
import omit from 'lodash/omit'
|
|
5
|
-
import { NotAuthenticated } from '@feathersjs/errors'
|
|
6
|
-
import { Query, Params } from '@feathersjs/feathers'
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const debug = createDebug('@feathersjs/authentication-local/strategy');
|
|
2
|
+
import bcrypt from 'bcryptjs'
|
|
3
|
+
import get from 'lodash/get'
|
|
4
|
+
import omit from 'lodash/omit'
|
|
5
|
+
import { NotAuthenticated } from '@feathersjs/errors'
|
|
6
|
+
import { Query, Params } from '@feathersjs/feathers'
|
|
7
|
+
import { AuthenticationRequest, AuthenticationBaseStrategy } from '@feathersjs/authentication'
|
|
8
|
+
import { createDebug } from '@feathersjs/commons'
|
|
9
|
+
|
|
10
|
+
const debug = createDebug('@feathersjs/authentication-local/strategy')
|
|
13
11
|
|
|
14
12
|
export class LocalStrategy extends AuthenticationBaseStrategy {
|
|
15
|
-
verifyConfiguration
|
|
16
|
-
const config = this.configuration
|
|
13
|
+
verifyConfiguration() {
|
|
14
|
+
const config = this.configuration
|
|
17
15
|
|
|
18
|
-
[
|
|
16
|
+
;['usernameField', 'passwordField'].forEach((prop) => {
|
|
19
17
|
if (typeof config[prop] !== 'string') {
|
|
20
|
-
throw new Error(`'${this.name}' authentication strategy requires a '${prop}' setting`)
|
|
18
|
+
throw new Error(`'${this.name}' authentication strategy requires a '${prop}' setting`)
|
|
21
19
|
}
|
|
22
|
-
})
|
|
20
|
+
})
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
get configuration
|
|
26
|
-
const authConfig = this.authentication.configuration
|
|
27
|
-
const config = super.configuration || {}
|
|
23
|
+
get configuration() {
|
|
24
|
+
const authConfig = this.authentication.configuration
|
|
25
|
+
const config = super.configuration || {}
|
|
28
26
|
|
|
29
27
|
return {
|
|
30
28
|
hashSize: 10,
|
|
@@ -35,100 +33,110 @@ export class LocalStrategy extends AuthenticationBaseStrategy {
|
|
|
35
33
|
entityPasswordField: config.passwordField,
|
|
36
34
|
entityUsernameField: config.usernameField,
|
|
37
35
|
...config
|
|
38
|
-
}
|
|
36
|
+
}
|
|
39
37
|
}
|
|
40
38
|
|
|
41
|
-
async getEntityQuery
|
|
39
|
+
async getEntityQuery(query: Query, _params: Params) {
|
|
42
40
|
return {
|
|
43
41
|
$limit: 1,
|
|
44
42
|
...query
|
|
45
|
-
}
|
|
43
|
+
}
|
|
46
44
|
}
|
|
47
45
|
|
|
48
|
-
async findEntity
|
|
49
|
-
const { entityUsernameField, errorMessage } = this.configuration
|
|
50
|
-
if (!username) {
|
|
51
|
-
|
|
46
|
+
async findEntity(username: string, params: Params) {
|
|
47
|
+
const { entityUsernameField, errorMessage } = this.configuration
|
|
48
|
+
if (!username) {
|
|
49
|
+
// don't query for users without any condition set.
|
|
50
|
+
throw new NotAuthenticated(errorMessage)
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
const query = await this.getEntityQuery(
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
const query = await this.getEntityQuery(
|
|
54
|
+
{
|
|
55
|
+
[entityUsernameField]: username
|
|
56
|
+
},
|
|
57
|
+
params
|
|
58
|
+
)
|
|
57
59
|
|
|
58
|
-
const findParams = Object.assign({}, params, { query })
|
|
59
|
-
const entityService = this.entityService
|
|
60
|
+
const findParams = Object.assign({}, params, { query })
|
|
61
|
+
const entityService = this.entityService
|
|
60
62
|
|
|
61
|
-
debug('Finding entity with query', params.query)
|
|
63
|
+
debug('Finding entity with query', params.query)
|
|
62
64
|
|
|
63
|
-
const result = await entityService.find(findParams)
|
|
64
|
-
const list = Array.isArray(result) ? result : result.data
|
|
65
|
+
const result = await entityService.find(findParams)
|
|
66
|
+
const list = Array.isArray(result) ? result : result.data
|
|
65
67
|
|
|
66
68
|
if (!Array.isArray(list) || list.length === 0) {
|
|
67
|
-
debug('No entity found')
|
|
69
|
+
debug('No entity found')
|
|
68
70
|
|
|
69
|
-
throw new NotAuthenticated(errorMessage)
|
|
71
|
+
throw new NotAuthenticated(errorMessage)
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
const [
|
|
74
|
+
const [entity] = list
|
|
73
75
|
|
|
74
|
-
return entity
|
|
76
|
+
return entity
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
async getEntity
|
|
78
|
-
const entityService = this.entityService
|
|
79
|
-
const { entityId = (entityService as any).id, entity } = this.configuration
|
|
79
|
+
async getEntity(result: any, params: Params) {
|
|
80
|
+
const entityService = this.entityService
|
|
81
|
+
const { entityId = (entityService as any).id, entity } = this.configuration
|
|
80
82
|
|
|
81
83
|
if (!entityId || result[entityId] === undefined) {
|
|
82
|
-
throw new NotAuthenticated('Could not get local entity')
|
|
84
|
+
throw new NotAuthenticated('Could not get local entity')
|
|
83
85
|
}
|
|
84
86
|
|
|
85
87
|
if (!params.provider) {
|
|
86
|
-
return result
|
|
88
|
+
return result
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
return entityService.get(result[entityId], {
|
|
90
92
|
...params,
|
|
91
93
|
[entity]: result
|
|
92
|
-
})
|
|
94
|
+
})
|
|
93
95
|
}
|
|
94
96
|
|
|
95
|
-
async comparePassword
|
|
96
|
-
const { entityPasswordField, errorMessage } = this.configuration
|
|
97
|
+
async comparePassword(entity: any, password: string) {
|
|
98
|
+
const { entityPasswordField, errorMessage } = this.configuration
|
|
97
99
|
// find password in entity, this allows for dot notation
|
|
98
|
-
const hash = get(entity, entityPasswordField)
|
|
100
|
+
const hash = get(entity, entityPasswordField)
|
|
99
101
|
|
|
100
102
|
if (!hash) {
|
|
101
|
-
debug(`Record is missing the '${entityPasswordField}' password field`)
|
|
103
|
+
debug(`Record is missing the '${entityPasswordField}' password field`)
|
|
102
104
|
|
|
103
|
-
throw new NotAuthenticated(errorMessage)
|
|
105
|
+
throw new NotAuthenticated(errorMessage)
|
|
104
106
|
}
|
|
105
107
|
|
|
106
|
-
debug('Verifying password')
|
|
108
|
+
debug('Verifying password')
|
|
107
109
|
|
|
108
|
-
const result = await bcrypt.compare(password, hash)
|
|
110
|
+
const result = await bcrypt.compare(password, hash)
|
|
109
111
|
|
|
110
112
|
if (result) {
|
|
111
|
-
return entity
|
|
113
|
+
return entity
|
|
112
114
|
}
|
|
113
115
|
|
|
114
|
-
throw new NotAuthenticated(errorMessage)
|
|
116
|
+
throw new NotAuthenticated(errorMessage)
|
|
115
117
|
}
|
|
116
118
|
|
|
117
|
-
async hashPassword
|
|
118
|
-
return bcrypt.hash(password, this.configuration.hashSize)
|
|
119
|
+
async hashPassword(password: string, _params: Params) {
|
|
120
|
+
return bcrypt.hash(password, this.configuration.hashSize)
|
|
119
121
|
}
|
|
120
122
|
|
|
121
|
-
async authenticate
|
|
122
|
-
const { passwordField, usernameField, entity } = this.configuration
|
|
123
|
-
const username = data[usernameField]
|
|
124
|
-
const password = data[passwordField]
|
|
125
|
-
const result = await this.findEntity(username, omit(params, 'provider'));
|
|
123
|
+
async authenticate(data: AuthenticationRequest, params: Params) {
|
|
124
|
+
const { passwordField, usernameField, entity, errorMessage } = this.configuration
|
|
125
|
+
const username = data[usernameField]
|
|
126
|
+
const password = data[passwordField]
|
|
126
127
|
|
|
127
|
-
|
|
128
|
+
if (!password) {
|
|
129
|
+
// exit early if there is no password
|
|
130
|
+
throw new NotAuthenticated(errorMessage)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const result = await this.findEntity(username, omit(params, 'provider'))
|
|
134
|
+
|
|
135
|
+
await this.comparePassword(result, password)
|
|
128
136
|
|
|
129
137
|
return {
|
|
130
138
|
authentication: { strategy: this.name },
|
|
131
139
|
[entity]: await this.getEntity(result, params)
|
|
132
|
-
}
|
|
140
|
+
}
|
|
133
141
|
}
|
|
134
142
|
}
|