UNPKG

3.77 kBPlain TextView Raw
1import bcrypt from 'bcryptjs';
2import get from 'lodash/get';
3import omit from 'lodash/omit';
4import Debug from 'debug';
5import { NotAuthenticated } from '@feathersjs/errors';
6import { Query, Params } from '@feathersjs/feathers';
7import {
8 AuthenticationRequest, AuthenticationBaseStrategy
9} from '@feathersjs/authentication';
10
11const debug = Debug('@feathersjs/authentication-local/strategy');
12
13export class LocalStrategy extends AuthenticationBaseStrategy {
14 verifyConfiguration () {
15 const config = this.configuration;
16
17 [ 'usernameField', 'passwordField' ].forEach(prop => {
18 if (typeof config[prop] !== 'string') {
19 throw new Error(`'${this.name}' authentication strategy requires a '${prop}' setting`);
20 }
21 });
22 }
23
24 get configuration () {
25 const authConfig = this.authentication.configuration;
26 const config = super.configuration || {};
27
28 return {
29 hashSize: 10,
30 service: authConfig.service,
31 entity: authConfig.entity,
32 entityId: authConfig.entityId,
33 errorMessage: 'Invalid login',
34 entityPasswordField: config.passwordField,
35 entityUsernameField: config.usernameField,
36 ...config
37 };
38 }
39
40 async getEntityQuery (query: Query, _params: Params) {
41 return {
42 $limit: 1,
43 ...query
44 };
45 }
46
47 async findEntity (username: string, params: Params) {
48 const { entityUsernameField, errorMessage } = this.configuration;
49 if (!username) { // don't query for users without any condition set.
50 throw new NotAuthenticated(errorMessage);
51 }
52
53 const query = await this.getEntityQuery({
54 [entityUsernameField]: username
55 }, params);
56
57 const findParams = Object.assign({}, params, { query });
58 const entityService = this.entityService;
59
60 debug('Finding entity with query', params.query);
61
62 const result = await entityService.find(findParams);
63 const list = Array.isArray(result) ? result : result.data;
64
65 if (!Array.isArray(list) || list.length === 0) {
66 debug(`No entity found`);
67
68 throw new NotAuthenticated(errorMessage);
69 }
70
71 const [ entity ] = list;
72
73 return entity;
74 }
75
76 async getEntity (result: any, params: Params) {
77 const entityService = this.entityService;
78 const { entityId = entityService.id, entity } = this.configuration;
79
80 if (!entityId || result[entityId] === undefined) {
81 throw new NotAuthenticated('Could not get local entity');
82 }
83
84 if (!params.provider) {
85 return result;
86 }
87
88 return entityService.get(result[entityId], {
89 ...params,
90 [entity]: result
91 });
92 }
93
94 async comparePassword (entity: any, password: string) {
95 const { entityPasswordField, errorMessage } = this.configuration;
96 // find password in entity, this allows for dot notation
97 const hash = get(entity, entityPasswordField);
98
99 if (!hash) {
100 debug(`Record is missing the '${entityPasswordField}' password field`);
101
102 throw new NotAuthenticated(errorMessage);
103 }
104
105 debug('Verifying password');
106
107 const result = await bcrypt.compare(password, hash);
108
109 if (result) {
110 return entity;
111 }
112
113 throw new NotAuthenticated(errorMessage);
114 }
115
116 async hashPassword (password: string, _params: Params) {
117 return bcrypt.hash(password, this.configuration.hashSize);
118 }
119
120 async authenticate (data: AuthenticationRequest, params: Params) {
121 const { passwordField, usernameField, entity } = this.configuration;
122 const username = data[usernameField];
123 const password = data[passwordField];
124 const result = await this.findEntity(username, omit(params, 'provider'));
125
126 await this.comparePassword(result, password);
127
128 return {
129 authentication: { strategy: this.name },
130 [entity]: await this.getEntity(result, params)
131 };
132 }
133}