@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/jwt.ts CHANGED
@@ -1,88 +1,97 @@
1
- /* eslint-disable @typescript-eslint/no-unused-vars */
2
- import omit from 'lodash/omit';
3
- import { IncomingMessage } from 'http';
4
- import { NotAuthenticated } from '@feathersjs/errors';
5
- import { Params } from '@feathersjs/feathers';
6
- import { createDebug } from '@feathersjs/commons';
1
+ /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/ban-ts-comment */
2
+ import omit from 'lodash/omit'
3
+ import { IncomingMessage } from 'http'
4
+ import { NotAuthenticated } from '@feathersjs/errors'
5
+ import { Params } from '@feathersjs/feathers'
6
+ import { createDebug } from '@feathersjs/commons'
7
7
  // @ts-ignore
8
- import lt from 'long-timeout';
8
+ import lt from 'long-timeout'
9
9
 
10
- import { AuthenticationBaseStrategy } from './strategy';
11
- import { AuthenticationRequest, AuthenticationResult, ConnectionEvent } from './core';
10
+ import { AuthenticationBaseStrategy } from './strategy'
11
+ import { AuthenticationParams, AuthenticationRequest, AuthenticationResult, ConnectionEvent } from './core'
12
12
 
13
- const debug = createDebug('@feathersjs/authentication/jwt');
14
- const SPLIT_HEADER = /(\S+)\s+(\S+)/;
13
+ const debug = createDebug('@feathersjs/authentication/jwt')
14
+ const SPLIT_HEADER = /(\S+)\s+(\S+)/
15
15
 
16
16
  export class JWTStrategy extends AuthenticationBaseStrategy {
17
- expirationTimers = new WeakMap();
17
+ expirationTimers = new WeakMap()
18
18
 
19
- get configuration () {
20
- const authConfig = this.authentication.configuration;
21
- const config = super.configuration;
19
+ get configuration() {
20
+ const authConfig = this.authentication.configuration
21
+ const config = super.configuration
22
22
 
23
23
  return {
24
24
  service: authConfig.service,
25
25
  entity: authConfig.entity,
26
26
  entityId: authConfig.entityId,
27
27
  header: 'Authorization',
28
- schemes: [ 'Bearer', 'JWT' ],
28
+ schemes: ['Bearer', 'JWT'],
29
29
  ...config
30
- };
30
+ }
31
31
  }
32
32
 
33
- async handleConnection (event: ConnectionEvent, connection: any, authResult?: AuthenticationResult): Promise<void> {
34
- const isValidLogout = event === 'logout' && connection.authentication && authResult &&
35
- connection.authentication.accessToken === authResult.accessToken;
33
+ async handleConnection(
34
+ event: ConnectionEvent,
35
+ connection: any,
36
+ authResult?: AuthenticationResult
37
+ ): Promise<void> {
38
+ const isValidLogout =
39
+ event === 'logout' &&
40
+ connection.authentication &&
41
+ authResult &&
42
+ connection.authentication.accessToken === authResult.accessToken
36
43
 
37
- const { accessToken } = authResult || {};
44
+ const { accessToken } = authResult || {}
38
45
 
39
46
  if (accessToken && event === 'login') {
40
- debug('Adding authentication information to connection');
41
- const { exp } = await this.authentication.verifyAccessToken(accessToken);
47
+ debug('Adding authentication information to connection')
48
+ const { exp } = await this.authentication.verifyAccessToken(accessToken)
42
49
  // The time (in ms) until the token expires
43
- const duration = (exp * 1000) - Date.now();
50
+ const duration = exp * 1000 - Date.now()
44
51
  // This may have to be a `logout` event but right now we don't want
45
52
  // the whole context object lingering around until the timer is gone
46
- const timer = lt.setTimeout(() => this.app.emit('disconnect', connection), duration);
53
+ const timer = lt.setTimeout(() => this.app.emit('disconnect', connection), duration)
47
54
 
48
- debug(`Registering connection expiration timer for ${duration}ms`);
49
- lt.clearTimeout(this.expirationTimers.get(connection));
50
- this.expirationTimers.set(connection, timer);
55
+ debug(`Registering connection expiration timer for ${duration}ms`)
56
+ lt.clearTimeout(this.expirationTimers.get(connection))
57
+ this.expirationTimers.set(connection, timer)
51
58
 
52
- debug('Adding authentication information to connection');
59
+ debug('Adding authentication information to connection')
53
60
  connection.authentication = {
54
61
  strategy: this.name,
55
62
  accessToken
56
- };
63
+ }
57
64
  } else if (event === 'disconnect' || isValidLogout) {
58
- debug('Removing authentication information and expiration timer from connection');
65
+ debug('Removing authentication information and expiration timer from connection')
59
66
 
60
- const { entity } = this.configuration;
67
+ const { entity } = this.configuration
61
68
 
62
- delete connection[entity];
63
- delete connection.authentication;
69
+ delete connection[entity]
70
+ delete connection.authentication
64
71
 
65
- lt.clearTimeout(this.expirationTimers.get(connection));
66
- this.expirationTimers.delete(connection);
72
+ lt.clearTimeout(this.expirationTimers.get(connection))
73
+ this.expirationTimers.delete(connection)
67
74
  }
68
75
  }
69
76
 
70
- verifyConfiguration () {
71
- const allowedKeys = [ 'entity', 'entityId', 'service', 'header', 'schemes' ];
77
+ verifyConfiguration() {
78
+ const allowedKeys = ['entity', 'entityId', 'service', 'header', 'schemes']
72
79
 
73
80
  for (const key of Object.keys(this.configuration)) {
74
81
  if (!allowedKeys.includes(key)) {
75
- throw new Error(`Invalid JwtStrategy option 'authentication.${this.name}.${key}'. Did you mean to set it in 'authentication.jwtOptions'?`);
82
+ throw new Error(
83
+ `Invalid JwtStrategy option 'authentication.${this.name}.${key}'. Did you mean to set it in 'authentication.jwtOptions'?`
84
+ )
76
85
  }
77
86
  }
78
87
 
79
88
  if (typeof this.configuration.header !== 'string') {
80
- throw new Error(`The 'header' option for the ${this.name} strategy must be a string`);
89
+ throw new Error(`The 'header' option for the ${this.name} strategy must be a string`)
81
90
  }
82
91
  }
83
92
 
84
- async getEntityQuery (_params: Params) {
85
- return {};
93
+ async getEntityQuery(_params: Params) {
94
+ return {}
86
95
  }
87
96
 
88
97
  /**
@@ -91,40 +100,40 @@ export class JWTStrategy extends AuthenticationBaseStrategy {
91
100
  * @param id The id to use
92
101
  * @param params Service call parameters
93
102
  */
94
- async getEntity (id: string, params: Params) {
95
- const entityService = this.entityService;
96
- const { entity } = this.configuration;
103
+ async getEntity(id: string, params: Params) {
104
+ const entityService = this.entityService
105
+ const { entity } = this.configuration
97
106
 
98
- debug('Getting entity', id);
107
+ debug('Getting entity', id)
99
108
 
100
109
  if (entityService === null) {
101
- throw new NotAuthenticated('Could not find entity service');
110
+ throw new NotAuthenticated('Could not find entity service')
102
111
  }
103
112
 
104
- const query = await this.getEntityQuery(params);
105
- const getParams = Object.assign({}, omit(params, 'provider'), { query });
106
- const result = await entityService.get(id, getParams);
113
+ const query = await this.getEntityQuery(params)
114
+ const getParams = Object.assign({}, omit(params, 'provider'), { query })
115
+ const result = await entityService.get(id, getParams)
107
116
 
108
117
  if (!params.provider) {
109
- return result;
118
+ return result
110
119
  }
111
120
 
112
- return entityService.get(id, { ...params, [entity]: result });
121
+ return entityService.get(id, { ...params, [entity]: result })
113
122
  }
114
123
 
115
- async getEntityId (authResult: AuthenticationResult, _params: Params) {
116
- return authResult.authentication.payload.sub;
124
+ async getEntityId(authResult: AuthenticationResult, _params: Params) {
125
+ return authResult.authentication.payload.sub
117
126
  }
118
127
 
119
- async authenticate (authentication: AuthenticationRequest, params: Params) {
120
- const { accessToken } = authentication;
121
- const { entity } = this.configuration;
128
+ async authenticate(authentication: AuthenticationRequest, params: AuthenticationParams) {
129
+ const { accessToken } = authentication
130
+ const { entity } = this.configuration
122
131
 
123
132
  if (!accessToken) {
124
- throw new NotAuthenticated('No access token');
133
+ throw new NotAuthenticated('No access token')
125
134
  }
126
135
 
127
- const payload = await this.authentication.verifyAccessToken(accessToken, params.jwt);
136
+ const payload = await this.authentication.verifyAccessToken(accessToken, params.jwt)
128
137
  const result = {
129
138
  accessToken,
130
139
  authentication: {
@@ -132,43 +141,44 @@ export class JWTStrategy extends AuthenticationBaseStrategy {
132
141
  accessToken,
133
142
  payload
134
143
  }
135
- };
144
+ }
136
145
 
137
146
  if (entity === null) {
138
- return result;
147
+ return result
139
148
  }
140
149
 
141
- const entityId = await this.getEntityId(result, params);
142
- const value = await this.getEntity(entityId, params);
150
+ const entityId = await this.getEntityId(result, params)
151
+ const value = await this.getEntity(entityId, params)
143
152
 
144
153
  return {
145
154
  ...result,
146
155
  [entity]: value
147
- };
156
+ }
148
157
  }
149
158
 
150
- async parse (req: IncomingMessage) {
151
- const { header, schemes }: { header: string, schemes: string[] } = this.configuration;
152
- const headerValue = req.headers && req.headers[header.toLowerCase()];
159
+ async parse(req: IncomingMessage): Promise<{
160
+ strategy: string
161
+ accessToken: string
162
+ } | null> {
163
+ const { header, schemes }: { header: string; schemes: string[] } = this.configuration
164
+ const headerValue = req.headers && req.headers[header.toLowerCase()]
153
165
 
154
166
  if (!headerValue || typeof headerValue !== 'string') {
155
- return null;
167
+ return null
156
168
  }
157
169
 
158
- debug('Found parsed header value');
170
+ debug('Found parsed header value')
159
171
 
160
- const [ , scheme, schemeValue ] = headerValue.match(SPLIT_HEADER) || [];
161
- const hasScheme = scheme && schemes.some(
162
- current => new RegExp(current, 'i').test(scheme)
163
- );
172
+ const [, scheme, schemeValue] = headerValue.match(SPLIT_HEADER) || []
173
+ const hasScheme = scheme && schemes.some((current) => new RegExp(current, 'i').test(scheme))
164
174
 
165
175
  if (scheme && !hasScheme) {
166
- return null;
176
+ return null
167
177
  }
168
178
 
169
179
  return {
170
180
  strategy: this.name,
171
181
  accessToken: hasScheme ? schemeValue : headerValue
172
- };
182
+ }
173
183
  }
174
184
  }
package/src/options.ts CHANGED
@@ -1,5 +1,7 @@
1
- export default {
2
- authStrategies: [],
1
+ import { FromSchema, authenticationSettingsSchema } from '@feathersjs/schema'
2
+
3
+ export const defaultOptions = {
4
+ authStrategies: [] as string[],
3
5
  jwtOptions: {
4
6
  header: { typ: 'access' }, // by default is an access token but can be any type
5
7
  audience: 'https://yourdomain.com', // The resource server where the token is processed
@@ -7,4 +9,8 @@ export default {
7
9
  algorithm: 'HS256',
8
10
  expiresIn: '1d'
9
11
  }
10
- };
12
+ }
13
+
14
+ export { authenticationSettingsSchema }
15
+
16
+ export type AuthenticationConfiguration = FromSchema<typeof authenticationSettingsSchema>
package/src/service.ts CHANGED
@@ -1,47 +1,53 @@
1
- import merge from 'lodash/merge';
2
- import { NotAuthenticated } from '@feathersjs/errors';
3
- import { AuthenticationBase, AuthenticationResult, AuthenticationRequest } from './core';
4
- import { connection, event } from './hooks';
5
- import '@feathersjs/transport-commons';
6
- import { createDebug } from '@feathersjs/commons';
7
- import { Params, ServiceMethods, ServiceAddons } from '@feathersjs/feathers';
8
- import jsonwebtoken from 'jsonwebtoken';
9
-
10
- const debug = createDebug('@feathersjs/authentication/service');
1
+ import merge from 'lodash/merge'
2
+ import { NotAuthenticated } from '@feathersjs/errors'
3
+ import { AuthenticationBase, AuthenticationResult, AuthenticationRequest, AuthenticationParams } from './core'
4
+ import { connection, event } from './hooks'
5
+ import '@feathersjs/transport-commons'
6
+ import { createDebug } from '@feathersjs/commons'
7
+ import { ServiceMethods, ServiceAddons } from '@feathersjs/feathers'
8
+ import { resolveDispatch } from '@feathersjs/schema'
9
+ import jsonwebtoken from 'jsonwebtoken'
10
+
11
+ const debug = createDebug('@feathersjs/authentication/service')
11
12
 
12
13
  declare module '@feathersjs/feathers/lib/declarations' {
13
- interface FeathersApplication<ServiceTypes, AppSettings> { // eslint-disable-line
14
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
15
+ interface FeathersApplication<Services, Settings> {
16
+ // eslint-disable-line
14
17
  /**
15
18
  * Returns the default authentication service or the
16
19
  * authentication service for a given path.
17
20
  *
18
21
  * @param location The service path to use (optional)
19
22
  */
20
- defaultAuthentication? (location?: string): AuthenticationService;
23
+ defaultAuthentication?(location?: string): AuthenticationService
21
24
  }
22
25
 
23
26
  interface Params {
24
- authenticated?: boolean;
25
- authentication?: AuthenticationRequest;
27
+ authenticated?: boolean
28
+ authentication?: AuthenticationRequest
26
29
  }
27
30
  }
28
31
 
29
- // eslint-disable-next-line
32
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
30
33
  export interface AuthenticationService extends ServiceAddons<AuthenticationResult, AuthenticationResult> {}
31
34
 
32
- export class AuthenticationService extends AuthenticationBase implements Partial<ServiceMethods<AuthenticationResult>> {
33
- constructor (app: any, configKey = 'authentication', options = {}) {
34
- super(app, configKey, options);
35
+ export class AuthenticationService
36
+ extends AuthenticationBase
37
+ implements Partial<ServiceMethods<AuthenticationResult, AuthenticationRequest, AuthenticationParams>>
38
+ {
39
+ constructor(app: any, configKey = 'authentication', options = {}) {
40
+ super(app, configKey, options)
35
41
 
36
42
  if (typeof app.defaultAuthentication !== 'function') {
37
43
  app.defaultAuthentication = function (location?: string) {
38
- const configKey = app.get('defaultAuthentication');
39
- const path = location || Object.keys(this.services).find(current =>
40
- this.service(current).configKey === configKey
41
- );
44
+ const configKey = app.get('defaultAuthentication')
45
+ const path =
46
+ location ||
47
+ Object.keys(this.services).find((current) => this.service(current).configKey === configKey)
42
48
 
43
- return path ? this.service(path) : null;
44
- };
49
+ return path ? this.service(path) : null
50
+ }
45
51
  }
46
52
  }
47
53
  /**
@@ -51,11 +57,11 @@ export class AuthenticationService extends AuthenticationBase implements Partial
51
57
  * @param _authResult The current authentication result
52
58
  * @param params The service call parameters
53
59
  */
54
- async getPayload (_authResult: AuthenticationResult, params: Params) {
60
+ async getPayload(_authResult: AuthenticationResult, params: AuthenticationParams) {
55
61
  // Uses `params.payload` or returns an empty payload
56
- const { payload = {} } = params;
62
+ const { payload = {} } = params
57
63
 
58
- return payload;
64
+ return payload
59
65
  }
60
66
 
61
67
  /**
@@ -65,24 +71,24 @@ export class AuthenticationService extends AuthenticationBase implements Partial
65
71
  * @param authResult The authentication result
66
72
  * @param params Service call parameters
67
73
  */
68
- async getTokenOptions (authResult: AuthenticationResult, params: Params) {
69
- const { service, entity, entityId } = this.configuration;
70
- const jwtOptions = merge({}, params.jwtOptions, params.jwt);
71
- const value = service && entity && authResult[entity];
74
+ async getTokenOptions(authResult: AuthenticationResult, params: AuthenticationParams) {
75
+ const { service, entity, entityId } = this.configuration
76
+ const jwtOptions = merge({}, params.jwtOptions, params.jwt)
77
+ const value = service && entity && authResult[entity]
72
78
 
73
79
  // Set the subject to the entity id if it is available
74
80
  if (value && !jwtOptions.subject) {
75
- const idProperty = entityId || this.app.service(service).id;
76
- const subject = value[idProperty];
81
+ const idProperty = entityId || this.app.service(service).id
82
+ const subject = value[idProperty]
77
83
 
78
84
  if (subject === undefined) {
79
- throw new NotAuthenticated(`Can not set subject from ${entity}.${idProperty}`);
85
+ throw new NotAuthenticated(`Can not set subject from ${entity}.${idProperty}`)
80
86
  }
81
87
 
82
- jwtOptions.subject = `${subject}`;
88
+ jwtOptions.subject = `${subject}`
83
89
  }
84
90
 
85
- return jwtOptions;
91
+ return jwtOptions
86
92
  }
87
93
 
88
94
  /**
@@ -92,36 +98,38 @@ export class AuthenticationService extends AuthenticationBase implements Partial
92
98
  * @param data The authentication request (should include `strategy` key)
93
99
  * @param params Service call parameters
94
100
  */
95
- async create (data: AuthenticationRequest, params?: Params) {
96
- const authStrategies = params.authStrategies || this.configuration.authStrategies;
101
+ async create(data: AuthenticationRequest, params?: AuthenticationParams) {
102
+ const authStrategies = params.authStrategies || this.configuration.authStrategies
97
103
 
98
104
  if (!authStrategies.length) {
99
- throw new NotAuthenticated('No authentication strategies allowed for creating a JWT (`authStrategies`)');
105
+ throw new NotAuthenticated('No authentication strategies allowed for creating a JWT (`authStrategies`)')
100
106
  }
101
107
 
102
- const authResult = await this.authenticate(data, params, ...authStrategies);
108
+ const authResult = await this.authenticate(data, params, ...authStrategies)
103
109
 
104
- debug('Got authentication result', authResult);
110
+ debug('Got authentication result', authResult)
105
111
 
106
112
  if (authResult.accessToken) {
107
- return authResult;
113
+ return authResult
108
114
  }
109
115
 
110
- const [ payload, jwtOptions ] = await Promise.all([
116
+ const [payload, jwtOptions] = await Promise.all([
111
117
  this.getPayload(authResult, params),
112
118
  this.getTokenOptions(authResult, params)
113
- ]);
119
+ ])
114
120
 
115
- debug('Creating JWT with', payload, jwtOptions);
121
+ debug('Creating JWT with', payload, jwtOptions)
116
122
 
117
- const accessToken = await this.createAccessToken(payload, jwtOptions, params.secret);
123
+ const accessToken = await this.createAccessToken(payload, jwtOptions, params.secret)
118
124
 
119
- return merge({ accessToken }, authResult, {
125
+ return {
126
+ accessToken,
127
+ ...authResult,
120
128
  authentication: {
121
- accessToken,
122
- payload: jsonwebtoken.decode(accessToken)
129
+ ...authResult.authentication,
130
+ payload: jsonwebtoken.decode(accessToken)
123
131
  }
124
- });
132
+ }
125
133
  }
126
134
 
127
135
  /**
@@ -131,59 +139,63 @@ export class AuthenticationService extends AuthenticationBase implements Partial
131
139
  * @param id The JWT to remove or null
132
140
  * @param params Service call parameters
133
141
  */
134
- async remove (id: string | null, params?: Params) {
135
- const { authentication } = params;
136
- const { authStrategies } = this.configuration;
142
+ async remove(id: string | null, params?: AuthenticationParams) {
143
+ const { authentication } = params
144
+ const { authStrategies } = this.configuration
137
145
 
138
146
  // When an id is passed it is expected to be the authentication `accessToken`
139
147
  if (id !== null && id !== authentication.accessToken) {
140
- throw new NotAuthenticated('Invalid access token');
148
+ throw new NotAuthenticated('Invalid access token')
141
149
  }
142
150
 
143
- debug('Verifying authentication strategy in remove');
151
+ debug('Verifying authentication strategy in remove')
144
152
 
145
- return this.authenticate(authentication, params, ...authStrategies);
153
+ return this.authenticate(authentication, params, ...authStrategies)
146
154
  }
147
155
 
148
156
  /**
149
157
  * Validates the service configuration.
150
158
  */
151
- async setup () {
159
+ async setup() {
160
+ await super.setup()
161
+
152
162
  // The setup method checks for valid settings and registers the
153
163
  // connection and event (login, logout) hooks
154
- const { secret, service, entity, entityId } = this.configuration;
164
+ const { secret, service, entity, entityId } = this.configuration
155
165
 
156
166
  if (typeof secret !== 'string') {
157
- throw new Error('A \'secret\' must be provided in your authentication configuration');
167
+ throw new Error("A 'secret' must be provided in your authentication configuration")
158
168
  }
159
169
 
160
170
  if (entity !== null) {
161
171
  if (service === undefined) {
162
- throw new Error('The \'service\' option is not set in the authentication configuration');
172
+ throw new Error("The 'service' option is not set in the authentication configuration")
163
173
  }
164
174
 
165
175
  if (this.app.service(service) === undefined) {
166
- throw new Error(`The '${service}' entity service does not exist (set to 'null' if it is not required)`);
176
+ throw new Error(
177
+ `The '${service}' entity service does not exist (set to 'null' if it is not required)`
178
+ )
167
179
  }
168
180
 
169
181
  if (this.app.service(service).id === undefined && entityId === undefined) {
170
- throw new Error(`The '${service}' service does not have an 'id' property and no 'entityId' option is set.`);
182
+ throw new Error(
183
+ `The '${service}' service does not have an 'id' property and no 'entityId' option is set.`
184
+ )
171
185
  }
172
186
  }
173
187
 
174
- (this as any).hooks({
175
- after: {
176
- create: [ connection('login'), event('login') ],
177
- remove: [ connection('logout'), event('logout') ]
178
- }
179
- });
188
+ this.hooks({
189
+ create: [resolveDispatch(), connection('login'), event('login')],
190
+ remove: [resolveDispatch(), connection('logout'), event('logout')]
191
+ } as any)
180
192
 
181
193
  this.app.on('disconnect', async (connection) => {
182
- await this.handleConnection('disconnect', connection);
183
- });
194
+ await this.handleConnection('disconnect', connection)
195
+ })
184
196
 
185
197
  if (typeof this.publish === 'function') {
186
- this.publish(() => null);
198
+ this.publish(() => null)
187
199
  }
188
200
  }
189
201
  }
package/src/strategy.ts CHANGED
@@ -1,34 +1,34 @@
1
- import { AuthenticationStrategy, AuthenticationBase } from './core';
2
- import { Application, Service } from '@feathersjs/feathers';
1
+ import { AuthenticationStrategy, AuthenticationBase } from './core'
2
+ import { Application, Service } from '@feathersjs/feathers'
3
3
 
4
4
  export class AuthenticationBaseStrategy implements AuthenticationStrategy {
5
- authentication?: AuthenticationBase;
6
- app?: Application;
7
- name?: string;
5
+ authentication?: AuthenticationBase
6
+ app?: Application
7
+ name?: string
8
8
 
9
- setAuthentication (auth: AuthenticationBase) {
10
- this.authentication = auth;
9
+ setAuthentication(auth: AuthenticationBase) {
10
+ this.authentication = auth
11
11
  }
12
12
 
13
- setApplication (app: Application) {
14
- this.app = app;
13
+ setApplication(app: Application) {
14
+ this.app = app
15
15
  }
16
16
 
17
- setName (name: string) {
18
- this.name = name;
17
+ setName(name: string) {
18
+ this.name = name
19
19
  }
20
20
 
21
- get configuration () {
22
- return this.authentication.configuration[this.name];
21
+ get configuration(): any {
22
+ return this.authentication.configuration[this.name]
23
23
  }
24
24
 
25
- get entityService (): Service<any> {
26
- const { service } = this.configuration;
25
+ get entityService(): Service {
26
+ const { service } = this.configuration
27
27
 
28
28
  if (!service) {
29
- return null;
29
+ return null
30
30
  }
31
31
 
32
- return this.app.service(service) || null;
32
+ return this.app.service(service) || null
33
33
  }
34
34
  }