@feathersjs/authentication-local 5.0.0-pre.9 → 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.
@@ -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
- export default function hashPassword (field: string, options: HashPasswordOptions = {}) {
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<any, any>, next?: NextFunction) => {
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 [ localStrategy ] = authService.getStrategies(strategy) as LocalStrategy[];
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) ? await Promise.all(data.map(addHashedPassword)) :
52
- await addHashedPassword(data);
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
- await next();
64
+ return next()
57
65
  }
58
- };
66
+ }
59
67
  }
@@ -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
- export default (...fields: string[]) => async (context: HookContext<any, any>, next?: NextFunction) => {
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
- const result = context.dispatch || context.result;
21
-
22
- if (result) {
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
- if (context.params && context.params.provider) {
34
- context.result = context.dispatch;
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 hashPassword from './hooks/hash-password';
2
- import protect from './hooks/protect';
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 } from './strategy';
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
- AuthenticationRequest, AuthenticationBaseStrategy
9
- } from '@feathersjs/authentication';
10
- import { createDebug } from '@feathersjs/commons';
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
- [ 'usernameField', 'passwordField' ].forEach(prop => {
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 (query: Query, _params: Params) {
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 (username: string, params: Params) {
49
- const { entityUsernameField, errorMessage } = this.configuration;
50
- if (!username) { // don't query for users without any condition set.
51
- throw new NotAuthenticated(errorMessage);
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
- [entityUsernameField]: username
56
- }, params);
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 [ entity ] = list;
74
+ const [entity] = list
73
75
 
74
- return entity;
76
+ return entity
75
77
  }
76
78
 
77
- async getEntity (result: any, params: Params) {
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 (entity: any, password: string) {
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 (password: string, _params: Params) {
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 (data: AuthenticationRequest, params: Params) {
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
- await this.comparePassword(result, password);
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
  }