@feathersjs/authentication 5.0.0-pre.2 → 5.0.0-pre.20
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 +183 -0
- package/LICENSE +1 -1
- package/README.md +2 -2
- package/lib/core.d.ts +27 -3
- package/lib/core.js +85 -79
- package/lib/core.js.map +1 -1
- package/lib/hooks/authenticate.d.ts +3 -3
- package/lib/hooks/authenticate.js +11 -20
- package/lib/hooks/authenticate.js.map +1 -1
- package/lib/hooks/connection.d.ts +2 -2
- package/lib/hooks/connection.js +7 -17
- package/lib/hooks/connection.js.map +1 -1
- package/lib/hooks/event.d.ts +2 -2
- package/lib/hooks/event.js +5 -17
- package/lib/hooks/event.js.map +1 -1
- package/lib/index.d.ts +4 -5
- package/lib/index.js +11 -6
- package/lib/index.js.map +1 -1
- package/lib/jwt.d.ts +3 -3
- package/lib/jwt.js +95 -106
- package/lib/jwt.js.map +1 -1
- package/lib/options.d.ts +118 -3
- package/lib/options.js +106 -1
- package/lib/options.js.map +1 -1
- package/lib/service.d.ts +10 -8
- package/lib/service.js +73 -93
- package/lib/service.js.map +1 -1
- package/lib/strategy.d.ts +1 -1
- package/package.json +20 -20
- package/src/core.ts +47 -9
- package/src/hooks/authenticate.ts +8 -9
- package/src/hooks/connection.ts +9 -11
- package/src/hooks/event.ts +6 -6
- package/src/index.ts +4 -6
- package/src/jwt.ts +8 -5
- package/src/options.ts +107 -2
- package/src/service.ts +14 -14
- package/src/strategy.ts +1 -1
package/lib/options.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.authenticationSettingsSchema = exports.defaultOptions = void 0;
|
|
4
|
+
exports.defaultOptions = {
|
|
4
5
|
authStrategies: [],
|
|
5
6
|
jwtOptions: {
|
|
6
7
|
header: { typ: 'access' },
|
|
@@ -10,4 +11,108 @@ exports.default = {
|
|
|
10
11
|
expiresIn: '1d'
|
|
11
12
|
}
|
|
12
13
|
};
|
|
14
|
+
exports.authenticationSettingsSchema = {
|
|
15
|
+
type: 'object',
|
|
16
|
+
required: ['secret', 'entity', 'authStrategies'],
|
|
17
|
+
properties: {
|
|
18
|
+
secret: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
description: 'The JWT signing secret'
|
|
21
|
+
},
|
|
22
|
+
entity: {
|
|
23
|
+
oneOf: [{
|
|
24
|
+
type: 'null'
|
|
25
|
+
}, {
|
|
26
|
+
type: 'string'
|
|
27
|
+
}],
|
|
28
|
+
description: 'The name of the authentication entity (e.g. user)'
|
|
29
|
+
},
|
|
30
|
+
entityId: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
description: 'The name of the authentication entity id property'
|
|
33
|
+
},
|
|
34
|
+
service: {
|
|
35
|
+
type: 'string',
|
|
36
|
+
description: 'The path of the entity service'
|
|
37
|
+
},
|
|
38
|
+
authStrategies: {
|
|
39
|
+
type: 'array',
|
|
40
|
+
items: { type: 'string' },
|
|
41
|
+
description: 'A list of authentication strategy names that are allowed to create JWT access tokens'
|
|
42
|
+
},
|
|
43
|
+
parseStrategies: {
|
|
44
|
+
type: 'array',
|
|
45
|
+
items: { type: 'string' },
|
|
46
|
+
description: 'A list of authentication strategy names that should parse HTTP headers for authentication information (defaults to `authStrategies`)'
|
|
47
|
+
},
|
|
48
|
+
jwtOptions: {
|
|
49
|
+
type: 'object'
|
|
50
|
+
},
|
|
51
|
+
jwt: {
|
|
52
|
+
type: 'object',
|
|
53
|
+
properties: {
|
|
54
|
+
header: {
|
|
55
|
+
type: 'string',
|
|
56
|
+
default: 'Authorization',
|
|
57
|
+
description: 'The HTTP header containing the JWT'
|
|
58
|
+
},
|
|
59
|
+
schemes: {
|
|
60
|
+
type: 'array',
|
|
61
|
+
items: { type: 'string' },
|
|
62
|
+
description: 'An array of schemes to support'
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
local: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
required: ['usernameField', 'passwordField'],
|
|
69
|
+
properties: {
|
|
70
|
+
usernameField: {
|
|
71
|
+
type: 'string',
|
|
72
|
+
description: 'Name of the username field (e.g. `email`)'
|
|
73
|
+
},
|
|
74
|
+
passwordField: {
|
|
75
|
+
type: 'string',
|
|
76
|
+
description: 'Name of the password field (e.g. `password`)'
|
|
77
|
+
},
|
|
78
|
+
hashSize: {
|
|
79
|
+
type: 'number',
|
|
80
|
+
description: 'The BCrypt salt length'
|
|
81
|
+
},
|
|
82
|
+
errorMessage: {
|
|
83
|
+
type: 'string',
|
|
84
|
+
default: 'Invalid login',
|
|
85
|
+
description: 'The error message to return on errors'
|
|
86
|
+
},
|
|
87
|
+
entityUsernameField: {
|
|
88
|
+
type: 'string',
|
|
89
|
+
description: 'Name of the username field on the entity if authentication request data and entity field names are different'
|
|
90
|
+
},
|
|
91
|
+
entityPasswordField: {
|
|
92
|
+
type: 'string',
|
|
93
|
+
description: 'Name of the password field on the entity if authentication request data and entity field names are different'
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
oauth: {
|
|
98
|
+
type: 'object',
|
|
99
|
+
properties: {
|
|
100
|
+
redirect: {
|
|
101
|
+
type: 'string'
|
|
102
|
+
},
|
|
103
|
+
origins: {
|
|
104
|
+
type: 'array',
|
|
105
|
+
items: { type: 'string' }
|
|
106
|
+
},
|
|
107
|
+
defaults: {
|
|
108
|
+
type: 'object',
|
|
109
|
+
properties: {
|
|
110
|
+
key: { type: 'string' },
|
|
111
|
+
secret: { type: 'string' }
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
13
118
|
//# sourceMappingURL=options.js.map
|
package/lib/options.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"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"}
|
package/lib/service.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { AuthenticationBase, AuthenticationResult, AuthenticationRequest } from './core';
|
|
1
|
+
import { AuthenticationBase, AuthenticationResult, AuthenticationRequest, AuthenticationParams } from './core';
|
|
2
2
|
import '@feathersjs/transport-commons';
|
|
3
|
-
import {
|
|
3
|
+
import { ServiceMethods, ServiceAddons } from '@feathersjs/feathers';
|
|
4
4
|
declare module '@feathersjs/feathers/lib/declarations' {
|
|
5
|
-
interface FeathersApplication<
|
|
5
|
+
interface FeathersApplication<Services, Settings> {
|
|
6
6
|
/**
|
|
7
7
|
* Returns the default authentication service or the
|
|
8
8
|
* authentication service for a given path.
|
|
@@ -18,7 +18,7 @@ declare module '@feathersjs/feathers/lib/declarations' {
|
|
|
18
18
|
}
|
|
19
19
|
export interface AuthenticationService extends ServiceAddons<AuthenticationResult, AuthenticationResult> {
|
|
20
20
|
}
|
|
21
|
-
export declare class AuthenticationService extends AuthenticationBase implements Partial<ServiceMethods<AuthenticationResult>> {
|
|
21
|
+
export declare class AuthenticationService extends AuthenticationBase implements Partial<ServiceMethods<AuthenticationResult, AuthenticationRequest, AuthenticationParams>> {
|
|
22
22
|
constructor(app: any, configKey?: string, options?: {});
|
|
23
23
|
/**
|
|
24
24
|
* Return the payload for a JWT based on the authentication result.
|
|
@@ -27,7 +27,9 @@ export declare class AuthenticationService extends AuthenticationBase implements
|
|
|
27
27
|
* @param _authResult The current authentication result
|
|
28
28
|
* @param params The service call parameters
|
|
29
29
|
*/
|
|
30
|
-
getPayload(_authResult: AuthenticationResult, params:
|
|
30
|
+
getPayload(_authResult: AuthenticationResult, params: AuthenticationParams): Promise<{
|
|
31
|
+
[key: string]: any;
|
|
32
|
+
}>;
|
|
31
33
|
/**
|
|
32
34
|
* Returns the JWT options based on an authentication result.
|
|
33
35
|
* By default sets the JWT subject to the entity id.
|
|
@@ -35,7 +37,7 @@ export declare class AuthenticationService extends AuthenticationBase implements
|
|
|
35
37
|
* @param authResult The authentication result
|
|
36
38
|
* @param params Service call parameters
|
|
37
39
|
*/
|
|
38
|
-
getTokenOptions(authResult: AuthenticationResult, params:
|
|
40
|
+
getTokenOptions(authResult: AuthenticationResult, params: AuthenticationParams): Promise<any>;
|
|
39
41
|
/**
|
|
40
42
|
* Create and return a new JWT for a given authentication request.
|
|
41
43
|
* Will trigger the `login` event.
|
|
@@ -43,7 +45,7 @@ export declare class AuthenticationService extends AuthenticationBase implements
|
|
|
43
45
|
* @param data The authentication request (should include `strategy` key)
|
|
44
46
|
* @param params Service call parameters
|
|
45
47
|
*/
|
|
46
|
-
create(data: AuthenticationRequest, params?:
|
|
48
|
+
create(data: AuthenticationRequest, params?: AuthenticationParams): Promise<AuthenticationResult>;
|
|
47
49
|
/**
|
|
48
50
|
* Mark a JWT as removed. By default only verifies the JWT and returns the result.
|
|
49
51
|
* Triggers the `logout` event.
|
|
@@ -51,7 +53,7 @@ export declare class AuthenticationService extends AuthenticationBase implements
|
|
|
51
53
|
* @param id The JWT to remove or null
|
|
52
54
|
* @param params Service call parameters
|
|
53
55
|
*/
|
|
54
|
-
remove(id: string | null, params?:
|
|
56
|
+
remove(id: string | null, params?: AuthenticationParams): Promise<AuthenticationResult>;
|
|
55
57
|
/**
|
|
56
58
|
* Validates the service configuration.
|
|
57
59
|
*/
|
package/lib/service.js
CHANGED
|
@@ -1,26 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
4
|
};
|
|
14
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
6
|
exports.AuthenticationService = void 0;
|
|
16
|
-
const debug_1 = __importDefault(require("debug"));
|
|
17
7
|
const merge_1 = __importDefault(require("lodash/merge"));
|
|
18
8
|
const errors_1 = require("@feathersjs/errors");
|
|
19
9
|
const core_1 = require("./core");
|
|
20
10
|
const hooks_1 = require("./hooks");
|
|
21
11
|
require("@feathersjs/transport-commons");
|
|
12
|
+
const commons_1 = require("@feathersjs/commons");
|
|
22
13
|
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
23
|
-
const debug =
|
|
14
|
+
const debug = (0, commons_1.createDebug)('@feathersjs/authentication/service');
|
|
24
15
|
class AuthenticationService extends core_1.AuthenticationBase {
|
|
25
16
|
constructor(app, configKey = 'authentication', options = {}) {
|
|
26
17
|
super(app, configKey, options);
|
|
@@ -39,12 +30,10 @@ class AuthenticationService extends core_1.AuthenticationBase {
|
|
|
39
30
|
* @param _authResult The current authentication result
|
|
40
31
|
* @param params The service call parameters
|
|
41
32
|
*/
|
|
42
|
-
getPayload(_authResult, params) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return payload;
|
|
47
|
-
});
|
|
33
|
+
async getPayload(_authResult, params) {
|
|
34
|
+
// Uses `params.payload` or returns an empty payload
|
|
35
|
+
const { payload = {} } = params;
|
|
36
|
+
return payload;
|
|
48
37
|
}
|
|
49
38
|
/**
|
|
50
39
|
* Returns the JWT options based on an authentication result.
|
|
@@ -53,22 +42,20 @@ class AuthenticationService extends core_1.AuthenticationBase {
|
|
|
53
42
|
* @param authResult The authentication result
|
|
54
43
|
* @param params Service call parameters
|
|
55
44
|
*/
|
|
56
|
-
getTokenOptions(authResult, params) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
throw new errors_1.NotAuthenticated(`Can not set subject from ${entity}.${idProperty}`);
|
|
67
|
-
}
|
|
68
|
-
jwtOptions.subject = `${subject}`;
|
|
45
|
+
async getTokenOptions(authResult, params) {
|
|
46
|
+
const { service, entity, entityId } = this.configuration;
|
|
47
|
+
const jwtOptions = (0, merge_1.default)({}, params.jwtOptions, params.jwt);
|
|
48
|
+
const value = service && entity && authResult[entity];
|
|
49
|
+
// Set the subject to the entity id if it is available
|
|
50
|
+
if (value && !jwtOptions.subject) {
|
|
51
|
+
const idProperty = entityId || this.app.service(service).id;
|
|
52
|
+
const subject = value[idProperty];
|
|
53
|
+
if (subject === undefined) {
|
|
54
|
+
throw new errors_1.NotAuthenticated(`Can not set subject from ${entity}.${idProperty}`);
|
|
69
55
|
}
|
|
70
|
-
|
|
71
|
-
}
|
|
56
|
+
jwtOptions.subject = `${subject}`;
|
|
57
|
+
}
|
|
58
|
+
return jwtOptions;
|
|
72
59
|
}
|
|
73
60
|
/**
|
|
74
61
|
* Create and return a new JWT for a given authentication request.
|
|
@@ -77,29 +64,27 @@ class AuthenticationService extends core_1.AuthenticationBase {
|
|
|
77
64
|
* @param data The authentication request (should include `strategy` key)
|
|
78
65
|
* @param params Service call parameters
|
|
79
66
|
*/
|
|
80
|
-
create(data, params) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
67
|
+
async create(data, params) {
|
|
68
|
+
const authStrategies = params.authStrategies || this.configuration.authStrategies;
|
|
69
|
+
if (!authStrategies.length) {
|
|
70
|
+
throw new errors_1.NotAuthenticated('No authentication strategies allowed for creating a JWT (`authStrategies`)');
|
|
71
|
+
}
|
|
72
|
+
const authResult = await this.authenticate(data, params, ...authStrategies);
|
|
73
|
+
debug('Got authentication result', authResult);
|
|
74
|
+
if (authResult.accessToken) {
|
|
75
|
+
return authResult;
|
|
76
|
+
}
|
|
77
|
+
const [payload, jwtOptions] = await Promise.all([
|
|
78
|
+
this.getPayload(authResult, params),
|
|
79
|
+
this.getTokenOptions(authResult, params)
|
|
80
|
+
]);
|
|
81
|
+
debug('Creating JWT with', payload, jwtOptions);
|
|
82
|
+
const accessToken = await this.createAccessToken(payload, jwtOptions, params.secret);
|
|
83
|
+
return (0, merge_1.default)({ accessToken }, authResult, {
|
|
84
|
+
authentication: {
|
|
85
|
+
accessToken,
|
|
86
|
+
payload: jsonwebtoken_1.default.decode(accessToken)
|
|
90
87
|
}
|
|
91
|
-
const [payload, jwtOptions] = yield Promise.all([
|
|
92
|
-
this.getPayload(authResult, params),
|
|
93
|
-
this.getTokenOptions(authResult, params)
|
|
94
|
-
]);
|
|
95
|
-
debug('Creating JWT with', payload, jwtOptions);
|
|
96
|
-
const accessToken = yield this.createAccessToken(payload, jwtOptions, params.secret);
|
|
97
|
-
return merge_1.default({ accessToken }, authResult, {
|
|
98
|
-
authentication: {
|
|
99
|
-
accessToken,
|
|
100
|
-
payload: jsonwebtoken_1.default.decode(accessToken)
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
88
|
});
|
|
104
89
|
}
|
|
105
90
|
/**
|
|
@@ -109,53 +94,48 @@ class AuthenticationService extends core_1.AuthenticationBase {
|
|
|
109
94
|
* @param id The JWT to remove or null
|
|
110
95
|
* @param params Service call parameters
|
|
111
96
|
*/
|
|
112
|
-
remove(id, params) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return this.authenticate(authentication, params, ...authStrategies);
|
|
122
|
-
});
|
|
97
|
+
async remove(id, params) {
|
|
98
|
+
const { authentication } = params;
|
|
99
|
+
const { authStrategies } = this.configuration;
|
|
100
|
+
// When an id is passed it is expected to be the authentication `accessToken`
|
|
101
|
+
if (id !== null && id !== authentication.accessToken) {
|
|
102
|
+
throw new errors_1.NotAuthenticated('Invalid access token');
|
|
103
|
+
}
|
|
104
|
+
debug('Verifying authentication strategy in remove');
|
|
105
|
+
return this.authenticate(authentication, params, ...authStrategies);
|
|
123
106
|
}
|
|
124
107
|
/**
|
|
125
108
|
* Validates the service configuration.
|
|
126
109
|
*/
|
|
127
|
-
setup() {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
110
|
+
async setup() {
|
|
111
|
+
await super.setup();
|
|
112
|
+
// The setup method checks for valid settings and registers the
|
|
113
|
+
// connection and event (login, logout) hooks
|
|
114
|
+
const { secret, service, entity, entityId } = this.configuration;
|
|
115
|
+
if (typeof secret !== 'string') {
|
|
116
|
+
throw new Error('A \'secret\' must be provided in your authentication configuration');
|
|
117
|
+
}
|
|
118
|
+
if (entity !== null) {
|
|
119
|
+
if (service === undefined) {
|
|
120
|
+
throw new Error('The \'service\' option is not set in the authentication configuration');
|
|
134
121
|
}
|
|
135
|
-
if (
|
|
136
|
-
|
|
137
|
-
throw new Error('The \'service\' option is not set in the authentication configuration');
|
|
138
|
-
}
|
|
139
|
-
if (this.app.service(service) === undefined) {
|
|
140
|
-
throw new Error(`The '${service}' entity service does not exist (set to 'null' if it is not required)`);
|
|
141
|
-
}
|
|
142
|
-
if (this.app.service(service).id === undefined && entityId === undefined) {
|
|
143
|
-
throw new Error(`The '${service}' service does not have an 'id' property and no 'entityId' option is set.`);
|
|
144
|
-
}
|
|
122
|
+
if (this.app.service(service) === undefined) {
|
|
123
|
+
throw new Error(`The '${service}' entity service does not exist (set to 'null' if it is not required)`);
|
|
145
124
|
}
|
|
146
|
-
this.
|
|
147
|
-
|
|
148
|
-
create: [hooks_1.connection('login'), hooks_1.event('login')],
|
|
149
|
-
remove: [hooks_1.connection('logout'), hooks_1.event('logout')]
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
this.app.on('disconnect', (connection) => __awaiter(this, void 0, void 0, function* () {
|
|
153
|
-
yield this.handleConnection('disconnect', connection);
|
|
154
|
-
}));
|
|
155
|
-
if (typeof this.publish === 'function') {
|
|
156
|
-
this.publish(() => null);
|
|
125
|
+
if (this.app.service(service).id === undefined && entityId === undefined) {
|
|
126
|
+
throw new Error(`The '${service}' service does not have an 'id' property and no 'entityId' option is set.`);
|
|
157
127
|
}
|
|
128
|
+
}
|
|
129
|
+
this.hooks({
|
|
130
|
+
create: [(0, hooks_1.connection)('login'), (0, hooks_1.event)('login')],
|
|
131
|
+
remove: [(0, hooks_1.connection)('logout'), (0, hooks_1.event)('logout')]
|
|
132
|
+
});
|
|
133
|
+
this.app.on('disconnect', async (connection) => {
|
|
134
|
+
await this.handleConnection('disconnect', connection);
|
|
158
135
|
});
|
|
136
|
+
if (typeof this.publish === 'function') {
|
|
137
|
+
this.publish(() => null);
|
|
138
|
+
}
|
|
159
139
|
}
|
|
160
140
|
}
|
|
161
141
|
exports.AuthenticationService = AuthenticationService;
|
package/lib/service.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
|
package/lib/strategy.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@feathersjs/authentication",
|
|
3
3
|
"description": "Add Authentication to your FeathersJS app.",
|
|
4
|
-
"version": "5.0.0-pre.
|
|
4
|
+
"version": "5.0.0-pre.20",
|
|
5
5
|
"homepage": "https://feathersjs.com",
|
|
6
6
|
"main": "lib/",
|
|
7
7
|
"types": "lib/",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
},
|
|
17
17
|
"repository": {
|
|
18
18
|
"type": "git",
|
|
19
|
-
"url": "git://github.com/feathersjs/feathers.git"
|
|
19
|
+
"url": "git://github.com/feathersjs/feathers.git",
|
|
20
|
+
"directory": "packages/authentication"
|
|
20
21
|
},
|
|
21
22
|
"author": {
|
|
22
23
|
"name": "Feathers contributors",
|
|
@@ -42,8 +43,7 @@
|
|
|
42
43
|
"scripts": {
|
|
43
44
|
"prepublish": "npm run compile",
|
|
44
45
|
"compile": "shx rm -rf lib/ && tsc",
|
|
45
|
-
"test": "
|
|
46
|
-
"mocha": "mocha --config ../../.mocharc.json --recursive test/**.test.ts test/**/*.test.ts"
|
|
46
|
+
"test": "mocha --config ../../.mocharc.json --recursive test/**.test.ts test/**/*.test.ts"
|
|
47
47
|
},
|
|
48
48
|
"directories": {
|
|
49
49
|
"lib": "lib"
|
|
@@ -52,27 +52,27 @@
|
|
|
52
52
|
"access": "public"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@feathersjs/
|
|
56
|
-
"@feathersjs/
|
|
57
|
-
"@feathersjs/
|
|
58
|
-
"@
|
|
59
|
-
"
|
|
55
|
+
"@feathersjs/commons": "^5.0.0-pre.20",
|
|
56
|
+
"@feathersjs/errors": "^5.0.0-pre.20",
|
|
57
|
+
"@feathersjs/feathers": "^5.0.0-pre.20",
|
|
58
|
+
"@feathersjs/transport-commons": "^5.0.0-pre.20",
|
|
59
|
+
"@types/jsonwebtoken": "^8.5.8",
|
|
60
60
|
"jsonwebtoken": "^8.5.1",
|
|
61
61
|
"lodash": "^4.17.21",
|
|
62
62
|
"long-timeout": "^0.1.1",
|
|
63
63
|
"uuid": "^8.3.2"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@feathersjs/
|
|
67
|
-
"@
|
|
68
|
-
"@types/lodash": "^4.14.
|
|
69
|
-
"@types/mocha": "^
|
|
70
|
-
"@types/node": "^
|
|
71
|
-
"@types/uuid": "^8.3.
|
|
72
|
-
"mocha": "^
|
|
73
|
-
"shx": "^0.3.
|
|
74
|
-
"ts-node": "^
|
|
75
|
-
"typescript": "^4.
|
|
66
|
+
"@feathersjs/memory": "^5.0.0-pre.20",
|
|
67
|
+
"@feathersjs/schema": "^5.0.0-pre.20",
|
|
68
|
+
"@types/lodash": "^4.14.182",
|
|
69
|
+
"@types/mocha": "^9.1.1",
|
|
70
|
+
"@types/node": "^17.0.31",
|
|
71
|
+
"@types/uuid": "^8.3.4",
|
|
72
|
+
"mocha": "^10.0.0",
|
|
73
|
+
"shx": "^0.3.4",
|
|
74
|
+
"ts-node": "^10.7.0",
|
|
75
|
+
"typescript": "^4.6.4"
|
|
76
76
|
},
|
|
77
|
-
"gitHead": "
|
|
77
|
+
"gitHead": "54de749a0b392c7da726c668002b50cafaca530c"
|
|
78
78
|
}
|
package/src/core.ts
CHANGED
|
@@ -2,12 +2,12 @@ import merge from 'lodash/merge';
|
|
|
2
2
|
import jsonwebtoken, { SignOptions, Secret, VerifyOptions } from 'jsonwebtoken';
|
|
3
3
|
import { v4 as uuidv4 } from 'uuid';
|
|
4
4
|
import { NotAuthenticated } from '@feathersjs/errors';
|
|
5
|
-
import
|
|
5
|
+
import { createDebug } from '@feathersjs/commons';
|
|
6
6
|
import { Application, Params } from '@feathersjs/feathers';
|
|
7
7
|
import { IncomingMessage, ServerResponse } from 'http';
|
|
8
|
-
import defaultOptions from './options';
|
|
8
|
+
import { defaultOptions } from './options';
|
|
9
9
|
|
|
10
|
-
const debug =
|
|
10
|
+
const debug = createDebug('@feathersjs/authentication/base');
|
|
11
11
|
|
|
12
12
|
export interface AuthenticationResult {
|
|
13
13
|
[key: string]: any;
|
|
@@ -18,6 +18,14 @@ export interface AuthenticationRequest {
|
|
|
18
18
|
[key: string]: any;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
export interface AuthenticationParams extends Params {
|
|
22
|
+
payload?: { [key: string]: any };
|
|
23
|
+
jwtOptions?: SignOptions;
|
|
24
|
+
authStrategies?: string[];
|
|
25
|
+
secret?: string;
|
|
26
|
+
[key: string]: any;
|
|
27
|
+
}
|
|
28
|
+
|
|
21
29
|
export type ConnectionEvent = 'login' | 'logout' | 'disconnect';
|
|
22
30
|
|
|
23
31
|
export interface AuthenticationStrategy {
|
|
@@ -44,6 +52,12 @@ export interface AuthenticationStrategy {
|
|
|
44
52
|
* and throw an error if it is invalid.
|
|
45
53
|
*/
|
|
46
54
|
verifyConfiguration? (): void;
|
|
55
|
+
/**
|
|
56
|
+
* Implement this method to setup this strategy
|
|
57
|
+
* @param auth The AuthenticationService
|
|
58
|
+
* @param name The name of the strategy
|
|
59
|
+
*/
|
|
60
|
+
setup? (auth: AuthenticationBase, name: string): Promise<void>;
|
|
47
61
|
/**
|
|
48
62
|
* Authenticate an authentication request with this strategy.
|
|
49
63
|
* Should throw an error if the strategy did not succeed.
|
|
@@ -51,7 +65,7 @@ export interface AuthenticationStrategy {
|
|
|
51
65
|
* @param authentication The authentication request
|
|
52
66
|
* @param params The service call parameters
|
|
53
67
|
*/
|
|
54
|
-
authenticate? (authentication: AuthenticationRequest, params:
|
|
68
|
+
authenticate? (authentication: AuthenticationRequest, params: AuthenticationParams): Promise<AuthenticationResult>;
|
|
55
69
|
/**
|
|
56
70
|
* Update a real-time connection according to this strategy.
|
|
57
71
|
*
|
|
@@ -77,10 +91,9 @@ export interface JwtVerifyOptions extends VerifyOptions {
|
|
|
77
91
|
*/
|
|
78
92
|
export class AuthenticationBase {
|
|
79
93
|
app: Application;
|
|
94
|
+
strategies: { [key: string]: AuthenticationStrategy };
|
|
80
95
|
configKey: string;
|
|
81
|
-
|
|
82
|
-
[key: string]: AuthenticationStrategy;
|
|
83
|
-
};
|
|
96
|
+
isReady: boolean;
|
|
84
97
|
|
|
85
98
|
/**
|
|
86
99
|
* Create a new authentication service.
|
|
@@ -97,6 +110,7 @@ export class AuthenticationBase {
|
|
|
97
110
|
this.app = app;
|
|
98
111
|
this.strategies = {};
|
|
99
112
|
this.configKey = configKey;
|
|
113
|
+
this.isReady = false;
|
|
100
114
|
|
|
101
115
|
app.set('defaultAuthentication', app.get('defaultAuthentication') || configKey);
|
|
102
116
|
app.set(configKey, merge({}, app.get(configKey), options));
|
|
@@ -143,6 +157,10 @@ export class AuthenticationBase {
|
|
|
143
157
|
|
|
144
158
|
// Register strategy as name
|
|
145
159
|
this.strategies[name] = strategy;
|
|
160
|
+
|
|
161
|
+
if (this.isReady) {
|
|
162
|
+
strategy.setup?.(this, name);
|
|
163
|
+
}
|
|
146
164
|
}
|
|
147
165
|
|
|
148
166
|
/**
|
|
@@ -155,6 +173,16 @@ export class AuthenticationBase {
|
|
|
155
173
|
.filter(current => !!current);
|
|
156
174
|
}
|
|
157
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Returns a single strategy by name
|
|
178
|
+
*
|
|
179
|
+
* @param name The strategy name
|
|
180
|
+
* @returns The authentication strategy or undefined
|
|
181
|
+
*/
|
|
182
|
+
getStrategy (name: string) {
|
|
183
|
+
return this.strategies[name];
|
|
184
|
+
}
|
|
185
|
+
|
|
158
186
|
/**
|
|
159
187
|
* Create a new access token with payload and options.
|
|
160
188
|
*
|
|
@@ -200,7 +228,7 @@ export class AuthenticationBase {
|
|
|
200
228
|
const verified = jsonwebtoken.verify(accessToken, jwtSecret, options);
|
|
201
229
|
|
|
202
230
|
return verified as any;
|
|
203
|
-
} catch (error) {
|
|
231
|
+
} catch (error: any) {
|
|
204
232
|
throw new NotAuthenticated(error.message, error);
|
|
205
233
|
}
|
|
206
234
|
}
|
|
@@ -212,7 +240,7 @@ export class AuthenticationBase {
|
|
|
212
240
|
* @param params Service call parameters
|
|
213
241
|
* @param allowed A list of allowed strategy names
|
|
214
242
|
*/
|
|
215
|
-
async authenticate (authentication: AuthenticationRequest, params:
|
|
243
|
+
async authenticate (authentication: AuthenticationRequest, params: AuthenticationParams, ...allowed: string[]) {
|
|
216
244
|
const { strategy } = authentication || {};
|
|
217
245
|
const [ authStrategy ] = this.getStrategies(strategy);
|
|
218
246
|
const strategyAllowed = allowed.includes(strategy);
|
|
@@ -265,4 +293,14 @@ export class AuthenticationBase {
|
|
|
265
293
|
|
|
266
294
|
return null;
|
|
267
295
|
}
|
|
296
|
+
|
|
297
|
+
async setup () {
|
|
298
|
+
this.isReady = true;
|
|
299
|
+
|
|
300
|
+
for (const name of Object.keys(this.strategies)) {
|
|
301
|
+
const strategy = this.strategies[name];
|
|
302
|
+
|
|
303
|
+
await strategy.setup?.(this, name);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
268
306
|
}
|