@feathersjs/authentication 5.0.0-pre.3 → 5.0.0-pre.30

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/src/core.ts CHANGED
@@ -1,24 +1,32 @@
1
- import merge from 'lodash/merge';
2
- import jsonwebtoken, { SignOptions, Secret, VerifyOptions } from 'jsonwebtoken';
3
- import { v4 as uuidv4 } from 'uuid';
4
- import { NotAuthenticated } from '@feathersjs/errors';
5
- import { createDebug } from '@feathersjs/commons';
6
- import { Application, Params } from '@feathersjs/feathers';
7
- import { IncomingMessage, ServerResponse } from 'http';
8
- import defaultOptions from './options';
1
+ import merge from 'lodash/merge'
2
+ import jsonwebtoken, { SignOptions, Secret, VerifyOptions, Algorithm } from 'jsonwebtoken'
3
+ import { v4 as uuidv4 } from 'uuid'
4
+ import { NotAuthenticated } from '@feathersjs/errors'
5
+ import { createDebug } from '@feathersjs/commons'
6
+ import { Application, Params } from '@feathersjs/feathers'
7
+ import { IncomingMessage, ServerResponse } from 'http'
8
+ import { AuthenticationConfiguration, defaultOptions } from './options'
9
9
 
10
- const debug = createDebug('@feathersjs/authentication/base');
10
+ const debug = createDebug('@feathersjs/authentication/base')
11
11
 
12
12
  export interface AuthenticationResult {
13
- [key: string]: any;
13
+ [key: string]: any
14
14
  }
15
15
 
16
16
  export interface AuthenticationRequest {
17
- strategy?: string;
18
- [key: string]: any;
17
+ strategy?: string
18
+ [key: string]: any
19
19
  }
20
20
 
21
- export type ConnectionEvent = 'login' | 'logout' | 'disconnect';
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
+
29
+ export type ConnectionEvent = 'login' | 'logout' | 'disconnect'
22
30
 
23
31
  export interface AuthenticationStrategy {
24
32
  /**
@@ -26,24 +34,30 @@ export interface AuthenticationStrategy {
26
34
  *
27
35
  * @param auth The AuthenticationService
28
36
  */
29
- setAuthentication? (auth: AuthenticationBase): void;
37
+ setAuthentication?(auth: AuthenticationBase): void
30
38
  /**
31
39
  * Implement this method to get access to the Feathers application
32
40
  *
33
41
  * @param app The Feathers application instance
34
42
  */
35
- setApplication? (app: Application): void;
43
+ setApplication?(app: Application): void
36
44
  /**
37
45
  * Implement this method to get access to the strategy name
38
46
  *
39
47
  * @param name The name of the strategy
40
48
  */
41
- setName? (name: string): void;
49
+ setName?(name: string): void
42
50
  /**
43
51
  * Implement this method to verify the current configuration
44
52
  * and throw an error if it is invalid.
45
53
  */
46
- verifyConfiguration? (): void;
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,36 +65,38 @@ 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: Params): Promise<AuthenticationResult>;
68
+ authenticate?(
69
+ authentication: AuthenticationRequest,
70
+ params: AuthenticationParams
71
+ ): Promise<AuthenticationResult>
55
72
  /**
56
73
  * Update a real-time connection according to this strategy.
57
74
  *
58
75
  * @param connection The real-time connection
59
76
  * @param context The hook context
60
77
  */
61
- handleConnection? (event: ConnectionEvent, connection: any, authResult?: AuthenticationResult): Promise<void>;
78
+ handleConnection?(event: ConnectionEvent, connection: any, authResult?: AuthenticationResult): Promise<void>
62
79
  /**
63
80
  * Parse a basic HTTP request and response for authentication request information.
64
81
  *
65
82
  * @param req The HTTP request
66
83
  * @param res The HTTP response
67
84
  */
68
- parse? (req: IncomingMessage, res: ServerResponse): Promise<AuthenticationRequest | null>;
85
+ parse?(req: IncomingMessage, res: ServerResponse): Promise<AuthenticationRequest | null>
69
86
  }
70
87
 
71
88
  export interface JwtVerifyOptions extends VerifyOptions {
72
- algorithm?: string | string[];
89
+ algorithm?: string | string[]
73
90
  }
74
91
 
75
92
  /**
76
93
  * A base class for managing authentication strategies and creating and verifying JWTs
77
94
  */
78
95
  export class AuthenticationBase {
79
- app: Application;
80
- configKey: string;
81
- strategies: {
82
- [key: string]: AuthenticationStrategy;
83
- };
96
+ app: Application
97
+ strategies: { [key: string]: AuthenticationStrategy }
98
+ configKey: string
99
+ isReady: boolean
84
100
 
85
101
  /**
86
102
  * Create a new authentication service.
@@ -89,32 +105,33 @@ export class AuthenticationBase {
89
105
  * @param configKey The configuration key name in `app.get` (default: `authentication`)
90
106
  * @param options Optional initial options
91
107
  */
92
- constructor (app: Application, configKey = 'authentication', options = {}) {
108
+ constructor(app: Application, configKey = 'authentication', options = {}) {
93
109
  if (!app || typeof app.use !== 'function') {
94
- throw new Error('An application instance has to be passed to the authentication service');
110
+ throw new Error('An application instance has to be passed to the authentication service')
95
111
  }
96
112
 
97
- this.app = app;
98
- this.strategies = {};
99
- this.configKey = configKey;
113
+ this.app = app
114
+ this.strategies = {}
115
+ this.configKey = configKey
116
+ this.isReady = false
100
117
 
101
- app.set('defaultAuthentication', app.get('defaultAuthentication') || configKey);
102
- app.set(configKey, merge({}, app.get(configKey), options));
118
+ app.set('defaultAuthentication', app.get('defaultAuthentication') || configKey)
119
+ app.set(configKey, merge({}, app.get(configKey), options))
103
120
  }
104
121
 
105
122
  /**
106
123
  * Return the current configuration from the application
107
124
  */
108
- get configuration () {
125
+ get configuration(): AuthenticationConfiguration {
109
126
  // Always returns a copy of the authentication configuration
110
- return Object.assign({}, defaultOptions, this.app.get(this.configKey));
127
+ return Object.assign({}, defaultOptions, this.app.get(this.configKey))
111
128
  }
112
129
 
113
130
  /**
114
131
  * A list of all registered strategy names
115
132
  */
116
- get strategyNames () {
117
- return Object.keys(this.strategies);
133
+ get strategyNames() {
134
+ return Object.keys(this.strategies)
118
135
  }
119
136
 
120
137
  /**
@@ -123,26 +140,30 @@ export class AuthenticationBase {
123
140
  * @param name The name to register the strategy under
124
141
  * @param strategy The authentication strategy instance
125
142
  */
126
- register (name: string, strategy: AuthenticationStrategy) {
143
+ register(name: string, strategy: AuthenticationStrategy) {
127
144
  // Call the functions a strategy can implement
128
145
  if (typeof strategy.setName === 'function') {
129
- strategy.setName(name);
146
+ strategy.setName(name)
130
147
  }
131
148
 
132
149
  if (typeof strategy.setApplication === 'function') {
133
- strategy.setApplication(this.app);
150
+ strategy.setApplication(this.app)
134
151
  }
135
152
 
136
153
  if (typeof strategy.setAuthentication === 'function') {
137
- strategy.setAuthentication(this);
154
+ strategy.setAuthentication(this)
138
155
  }
139
156
 
140
157
  if (typeof strategy.verifyConfiguration === 'function') {
141
- strategy.verifyConfiguration();
158
+ strategy.verifyConfiguration()
142
159
  }
143
160
 
144
161
  // Register strategy as name
145
- this.strategies[name] = strategy;
162
+ this.strategies[name] = strategy
163
+
164
+ if (this.isReady) {
165
+ strategy.setup?.(this, name)
166
+ }
146
167
  }
147
168
 
148
169
  /**
@@ -150,9 +171,18 @@ export class AuthenticationBase {
150
171
  *
151
172
  * @param names The list or strategy names
152
173
  */
153
- getStrategies (...names: string[]) {
154
- return names.map(name => this.strategies[name])
155
- .filter(current => !!current);
174
+ getStrategies(...names: string[]) {
175
+ return names.map((name) => this.strategies[name]).filter((current) => !!current)
176
+ }
177
+
178
+ /**
179
+ * Returns a single strategy by name
180
+ *
181
+ * @param name The strategy name
182
+ * @returns The authentication strategy or undefined
183
+ */
184
+ getStrategy(name: string) {
185
+ return this.strategies[name]
156
186
  }
157
187
 
158
188
  /**
@@ -162,19 +192,23 @@ export class AuthenticationBase {
162
192
  * @param optsOverride The options to extend the defaults (`configuration.jwtOptions`) with
163
193
  * @param secretOverride Use a different secret instead
164
194
  */
165
- async createAccessToken (payload: string | Buffer | object, optsOverride?: SignOptions, secretOverride?: Secret) {
166
- const { secret, jwtOptions } = this.configuration;
195
+ async createAccessToken(
196
+ payload: string | Buffer | object,
197
+ optsOverride?: SignOptions,
198
+ secretOverride?: Secret
199
+ ) {
200
+ const { secret, jwtOptions } = this.configuration
167
201
  // Use configuration by default but allow overriding the secret
168
- const jwtSecret = secretOverride || secret;
202
+ const jwtSecret = secretOverride || secret
169
203
  // Default jwt options merged with additional options
170
- const options = merge({}, jwtOptions, optsOverride);
204
+ const options = merge({}, jwtOptions, optsOverride)
171
205
 
172
206
  if (!options.jwtid) {
173
207
  // Generate a UUID as JWT ID by default
174
- options.jwtid = uuidv4();
208
+ options.jwtid = uuidv4()
175
209
  }
176
210
 
177
- return jsonwebtoken.sign(payload, jwtSecret, options);
211
+ return jsonwebtoken.sign(payload, jwtSecret, options)
178
212
  }
179
213
 
180
214
  /**
@@ -184,24 +218,24 @@ export class AuthenticationBase {
184
218
  * @param optsOverride The options to extend the defaults (`configuration.jwtOptions`) with
185
219
  * @param secretOverride Use a different secret instead
186
220
  */
187
- async verifyAccessToken (accessToken: string, optsOverride?: JwtVerifyOptions, secretOverride?: Secret) {
188
- const { secret, jwtOptions } = this.configuration;
189
- const jwtSecret = secretOverride || secret;
190
- const options = merge({}, jwtOptions, optsOverride);
191
- const { algorithm } = options;
221
+ async verifyAccessToken(accessToken: string, optsOverride?: JwtVerifyOptions, secretOverride?: Secret) {
222
+ const { secret, jwtOptions } = this.configuration
223
+ const jwtSecret = secretOverride || secret
224
+ const options = merge({}, jwtOptions, optsOverride)
225
+ const { algorithm } = options
192
226
 
193
227
  // Normalize the `algorithm` setting into the algorithms array
194
228
  if (algorithm && !options.algorithms) {
195
- options.algorithms = Array.isArray(algorithm) ? algorithm : [ algorithm ];
196
- delete options.algorithm;
229
+ options.algorithms = (Array.isArray(algorithm) ? algorithm : [algorithm]) as Algorithm[]
230
+ delete options.algorithm
197
231
  }
198
232
 
199
233
  try {
200
- const verified = jsonwebtoken.verify(accessToken, jwtSecret, options);
234
+ const verified = jsonwebtoken.verify(accessToken, jwtSecret, options)
201
235
 
202
- return verified as any;
203
- } catch (error) {
204
- throw new NotAuthenticated(error.message, error);
236
+ return verified as any
237
+ } catch (error: any) {
238
+ throw new NotAuthenticated(error.message, error)
205
239
  }
206
240
  }
207
241
 
@@ -212,33 +246,40 @@ export class AuthenticationBase {
212
246
  * @param params Service call parameters
213
247
  * @param allowed A list of allowed strategy names
214
248
  */
215
- async authenticate (authentication: AuthenticationRequest, params: Params, ...allowed: string[]) {
216
- const { strategy } = authentication || {};
217
- const [ authStrategy ] = this.getStrategies(strategy);
218
- const strategyAllowed = allowed.includes(strategy);
249
+ async authenticate(
250
+ authentication: AuthenticationRequest,
251
+ params: AuthenticationParams,
252
+ ...allowed: string[]
253
+ ) {
254
+ const { strategy } = authentication || {}
255
+ const [authStrategy] = this.getStrategies(strategy)
256
+ const strategyAllowed = allowed.includes(strategy)
219
257
 
220
- debug('Running authenticate for strategy', strategy, allowed);
258
+ debug('Running authenticate for strategy', strategy, allowed)
221
259
 
222
260
  if (!authentication || !authStrategy || !strategyAllowed) {
223
- const additionalInfo = (!strategy && ' (no `strategy` set)') ||
224
- (!strategyAllowed && ' (strategy not allowed in authStrategies)') || '';
261
+ const additionalInfo =
262
+ (!strategy && ' (no `strategy` set)') ||
263
+ (!strategyAllowed && ' (strategy not allowed in authStrategies)') ||
264
+ ''
225
265
 
226
266
  // If there are no valid strategies or `authentication` is not an object
227
- throw new NotAuthenticated('Invalid authentication information' + additionalInfo);
267
+ throw new NotAuthenticated('Invalid authentication information' + additionalInfo)
228
268
  }
229
269
 
230
270
  return authStrategy.authenticate(authentication, {
231
271
  ...params,
232
272
  authenticated: true
233
- });
273
+ })
234
274
  }
235
275
 
236
- async handleConnection (event: ConnectionEvent, connection: any, authResult?: AuthenticationResult) {
237
- const strategies = this.getStrategies(...Object.keys(this.strategies))
238
- .filter(current => typeof current.handleConnection === 'function');
276
+ async handleConnection(event: ConnectionEvent, connection: any, authResult?: AuthenticationResult) {
277
+ const strategies = this.getStrategies(...Object.keys(this.strategies)).filter(
278
+ (current) => typeof current.handleConnection === 'function'
279
+ )
239
280
 
240
281
  for (const strategy of strategies) {
241
- await strategy.handleConnection(event, connection, authResult);
282
+ await strategy.handleConnection(event, connection, authResult)
242
283
  }
243
284
  }
244
285
 
@@ -249,20 +290,29 @@ export class AuthenticationBase {
249
290
  * @param res The HTTP response
250
291
  * @param names A list of strategies to use
251
292
  */
252
- async parse (req: IncomingMessage, res: ServerResponse, ...names: string[]) {
253
- const strategies = this.getStrategies(...names)
254
- .filter(current => typeof current.parse === 'function');
293
+ async parse(req: IncomingMessage, res: ServerResponse, ...names: string[]) {
294
+ const strategies = this.getStrategies(...names).filter((current) => typeof current.parse === 'function')
255
295
 
256
- debug('Strategies parsing HTTP header for authentication information', names);
296
+ debug('Strategies parsing HTTP header for authentication information', names)
257
297
 
258
298
  for (const authStrategy of strategies) {
259
- const value = await authStrategy.parse(req, res);
299
+ const value = await authStrategy.parse(req, res)
260
300
 
261
301
  if (value !== null) {
262
- return value;
302
+ return value
263
303
  }
264
304
  }
265
305
 
266
- return null;
306
+ return null
307
+ }
308
+
309
+ async setup() {
310
+ this.isReady = true
311
+
312
+ for (const name of Object.keys(this.strategies)) {
313
+ const strategy = this.strategies[name]
314
+
315
+ await strategy.setup?.(this, name)
316
+ }
267
317
  }
268
318
  }
@@ -1,64 +1,67 @@
1
- import flatten from 'lodash/flatten';
2
- import omit from 'lodash/omit';
3
- import { HookContext } from '@feathersjs/feathers';
4
- import { NotAuthenticated } from '@feathersjs/errors';
5
- import { createDebug } from '@feathersjs/commons';
1
+ import flatten from 'lodash/flatten'
2
+ import omit from 'lodash/omit'
3
+ import { HookContext, NextFunction } from '@feathersjs/feathers'
4
+ import { NotAuthenticated } from '@feathersjs/errors'
5
+ import { createDebug } from '@feathersjs/commons'
6
6
 
7
- const debug = createDebug('@feathersjs/authentication/hooks/authenticate');
7
+ const debug = createDebug('@feathersjs/authentication/hooks/authenticate')
8
8
 
9
9
  export interface AuthenticateHookSettings {
10
- service?: string;
11
- strategies: string[];
10
+ service?: string
11
+ strategies?: string[]
12
12
  }
13
13
 
14
14
  export default (originalSettings: string | AuthenticateHookSettings, ...originalStrategies: string[]) => {
15
- const settings = typeof originalSettings === 'string'
16
- ? { strategies: flatten([ originalSettings, ...originalStrategies ]) }
17
- : originalSettings;
15
+ const settings =
16
+ typeof originalSettings === 'string'
17
+ ? { strategies: flatten([originalSettings, ...originalStrategies]) }
18
+ : originalSettings
18
19
 
19
20
  if (!originalSettings || settings.strategies.length === 0) {
20
- throw new Error('The authenticate hook needs at least one allowed strategy');
21
+ throw new Error('The authenticate hook needs at least one allowed strategy')
21
22
  }
22
23
 
23
- return async (context: HookContext<any, any>) => {
24
- const { app, params, type, path, service } = context;
25
- const { strategies } = settings;
26
- const { provider, authentication } = params;
27
- const authService = app.defaultAuthentication(settings.service);
24
+ return async (context: HookContext, _next?: NextFunction) => {
25
+ const next = typeof _next === 'function' ? _next : async () => context
26
+ const { app, params, type, path, service } = context
27
+ const { strategies } = settings
28
+ const { provider, authentication } = params
29
+ const authService = app.defaultAuthentication(settings.service)
28
30
 
29
- debug(`Running authenticate hook on '${path}'`);
31
+ debug(`Running authenticate hook on '${path}'`)
30
32
 
31
33
  if (type && type !== 'before') {
32
- throw new NotAuthenticated('The authenticate hook must be used as a before hook');
34
+ throw new NotAuthenticated('The authenticate hook must be used as a before hook')
33
35
  }
34
36
 
35
37
  if (!authService || typeof authService.authenticate !== 'function') {
36
- throw new NotAuthenticated('Could not find a valid authentication service');
38
+ throw new NotAuthenticated('Could not find a valid authentication service')
37
39
  }
38
40
 
39
- // @ts-ignore
40
41
  if (service === authService) {
41
- throw new NotAuthenticated('The authenticate hook does not need to be used on the authentication service');
42
+ throw new NotAuthenticated(
43
+ 'The authenticate hook does not need to be used on the authentication service'
44
+ )
42
45
  }
43
46
 
44
47
  if (params.authenticated === true) {
45
- return context;
48
+ return next()
46
49
  }
47
50
 
48
51
  if (authentication) {
49
- const authParams = omit(params, 'provider', 'authentication');
52
+ const authParams = omit(params, 'provider', 'authentication')
50
53
 
51
- debug('Authenticating with', authentication, strategies);
54
+ debug('Authenticating with', authentication, strategies)
52
55
 
53
- const authResult = await authService.authenticate(authentication, authParams, ...strategies);
56
+ const authResult = await authService.authenticate(authentication, authParams, ...strategies)
54
57
 
55
- context.params = Object.assign({}, params, omit(authResult, 'accessToken'), { authenticated: true });
56
-
57
- return context;
58
+ context.params = Object.assign({}, params, omit(authResult, 'accessToken'), {
59
+ authenticated: true
60
+ })
58
61
  } else if (provider) {
59
- throw new NotAuthenticated('Not authenticated');
62
+ throw new NotAuthenticated('Not authenticated')
60
63
  }
61
64
 
62
- return context;
63
- };
64
- };
65
+ return next()
66
+ }
67
+ }
@@ -1,19 +1,20 @@
1
- import { HookContext } from '@feathersjs/feathers';
2
- import omit from 'lodash/omit';
3
- import { AuthenticationBase, ConnectionEvent } from '../core';
1
+ import { HookContext, NextFunction } from '@feathersjs/feathers'
2
+ import omit from 'lodash/omit'
3
+ import { AuthenticationBase, ConnectionEvent } from '../core'
4
4
 
5
- export default (event: ConnectionEvent) => async (context: HookContext) => {
6
- const { result, params: { connection } } = context;
5
+ export default (event: ConnectionEvent) => async (context: HookContext, next: NextFunction) => {
6
+ await next()
7
7
 
8
- if (!connection) {
9
- return context;
10
- }
11
-
12
- const service = context.service as unknown as AuthenticationBase;
8
+ const {
9
+ result,
10
+ params: { connection }
11
+ } = context
13
12
 
14
- Object.assign(connection, omit(result, 'accessToken', 'authentication'));
13
+ if (connection) {
14
+ const service = context.service as unknown as AuthenticationBase
15
15
 
16
- await service.handleConnection(event, connection, result);
16
+ Object.assign(connection, omit(result, 'accessToken', 'authentication'))
17
17
 
18
- return context;
19
- };
18
+ await service.handleConnection(event, connection, result)
19
+ }
20
+ }
@@ -1,16 +1,16 @@
1
- import { HookContext } from '@feathersjs/feathers';
2
- import { createDebug } from '@feathersjs/commons';
3
- import { ConnectionEvent } from '../core';
1
+ import { HookContext, NextFunction } from '@feathersjs/feathers'
2
+ import { createDebug } from '@feathersjs/commons'
3
+ import { ConnectionEvent } from '../core'
4
4
 
5
- const debug = createDebug('@feathersjs/authentication/hooks/connection');
5
+ const debug = createDebug('@feathersjs/authentication/hooks/connection')
6
6
 
7
- export default (event: ConnectionEvent) => async (context: HookContext) => {
8
- const { app, result, params } = context;
7
+ export default (event: ConnectionEvent) => async (context: HookContext, next: NextFunction) => {
8
+ await next()
9
+
10
+ const { app, result, params } = context
9
11
 
10
12
  if (params.provider && result) {
11
- debug(`Sending authentication event '${event}'`);
12
- app.emit(event, result, params, context);
13
+ debug(`Sending authentication event '${event}'`)
14
+ app.emit(event, result, params, context)
13
15
  }
14
-
15
- return context;
16
- };
16
+ }
@@ -1,3 +1,3 @@
1
- export { default as authenticate } from './authenticate';
2
- export { default as connection } from './connection';
3
- export { default as event } from './event';
1
+ export { default as authenticate } from './authenticate'
2
+ export { default as connection } from './connection'
3
+ export { default as event } from './event'
package/src/index.ts CHANGED
@@ -1,16 +1,14 @@
1
- import * as hooks from './hooks';
2
-
3
- const { authenticate } = hooks;
4
-
5
- export { hooks };
6
- export { authenticate };
1
+ export * as hooks from './hooks'
2
+ export { authenticate } from './hooks'
7
3
  export {
8
4
  AuthenticationBase,
9
5
  AuthenticationRequest,
10
6
  AuthenticationResult,
11
7
  AuthenticationStrategy,
8
+ AuthenticationParams,
12
9
  ConnectionEvent
13
- } from './core';
14
- export { AuthenticationBaseStrategy } from './strategy';
15
- export { AuthenticationService } from './service';
16
- export { JWTStrategy } from './jwt';
10
+ } from './core'
11
+ export { AuthenticationBaseStrategy } from './strategy'
12
+ export { AuthenticationService } from './service'
13
+ export { JWTStrategy } from './jwt'
14
+ export { authenticationSettingsSchema, AuthenticationConfiguration } from './options'