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