-
-
Notifications
You must be signed in to change notification settings - Fork 740
/
service.ts
169 lines (137 loc) · 5.74 KB
/
service.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
import Debug from 'debug';
import { merge, get } from 'lodash';
import { NotAuthenticated } from '@feathersjs/errors';
import { AuthenticationBase, AuthenticationResult, AuthenticationRequest } from './core';
import { connection, event } from './hooks';
import '@feathersjs/transport-commons';
import { Application, Params, ServiceMethods, ServiceAddons } from '@feathersjs/feathers';
const debug = Debug('@feathersjs/authentication/service');
declare module '@feathersjs/feathers' {
interface Application<ServiceTypes = {}> {
/**
* Returns the default authentication service or the
* authentication service for a given path.
*
* @param location The service path to use (optional)
*/
defaultAuthentication (location?: string): AuthenticationService;
}
}
export class AuthenticationService extends AuthenticationBase implements Partial<ServiceMethods<AuthenticationResult>> {
constructor (app: Application, configKey: string = 'authentication', options = {}) {
super(app, configKey, options);
if (typeof app.defaultAuthentication !== 'function') {
app.defaultAuthentication = function (location?: string) {
const configKey = app.get('defaultAuthentication');
const path = location || Object.keys(this.services).find(current =>
this.service(current).configKey === configKey
);
return path ? this.service(path) : null;
};
}
}
/**
* Return the payload for a JWT based on the authentication result.
* Called internally by the `create` method.
* @param _authResult The current authentication result
* @param params The service call parameters
*/
async getPayload (_authResult: AuthenticationResult, params: Params) {
// Uses `params.payload` or returns an empty payload
const { payload = {} } = params;
return payload;
}
/**
* Returns the JWT options based on an authentication result.
* By default sets the JWT subject to the entity id.
* @param authResult The authentication result
* @param params Service call parameters
*/
async getTokenOptions (authResult: AuthenticationResult, params: Params) {
const { service, entity, entityId } = this.configuration;
const jwtOptions = merge({}, params.jwtOptions, params.jwt);
const hasEntity = service && entity && authResult[entity];
// Set the subject to the entity id if it is available
if (hasEntity && !jwtOptions.subject) {
const idProperty = entityId || this.app.service(service).id;
const subject = get(authResult, [ entity, idProperty ]);
if (subject === undefined) {
throw new NotAuthenticated(`Can not set subject from ${entity}.${idProperty}`);
}
jwtOptions.subject = `${subject}`;
}
return jwtOptions;
}
/**
* Create and return a new JWT for a given authentication request.
* Will trigger the `login` event.
* @param data The authentication request (should include `strategy` key)
* @param params Service call parameters
*/
async create (data: AuthenticationRequest, params: Params) {
const authStrategies = params.authStrategies || this.configuration.authStrategies;
if (!authStrategies.length) {
throw new NotAuthenticated('No authentication strategies allowed for creating a JWT (`authStrategies`)');
}
const authResult = await this.authenticate(data, params, ...authStrategies);
debug('Got authentication result', authResult);
if (authResult.accessToken) {
return authResult;
}
const [ payload, jwtOptions ] = await Promise.all([
this.getPayload(authResult, params),
this.getTokenOptions(authResult, params)
]);
debug('Creating JWT with', payload, jwtOptions);
const accessToken = await this.createAccessToken(payload, jwtOptions, params.secret);
return Object.assign({}, { accessToken }, authResult);
}
/**
* Mark a JWT as removed. By default only verifies the JWT and returns the result.
* Triggers the `logout` event.
* @param id The JWT to remove or null
* @param params Service call parameters
*/
async remove (id: null|string, params: Params) {
const { authentication } = params;
const { authStrategies } = this.configuration;
// When an id is passed it is expected to be the authentication `accessToken`
if (id !== null && id !== authentication.accessToken) {
throw new NotAuthenticated('Invalid access token');
}
debug('Verifying authentication strategy in remove');
return this.authenticate(authentication, params, ...authStrategies);
}
/**
* Validates the service configuration.
*/
setup () {
// The setup method checks for valid settings and registers the
// connection and event (login, logout) hooks
const { secret, service, entity, entityId } = this.configuration;
const self = this as unknown as ServiceAddons<AuthenticationResult>;
if (typeof secret !== 'string') {
throw new Error(`A 'secret' must be provided in your authentication configuration`);
}
if (entity !== null) {
if (service === undefined) {
throw new Error(`The 'service' option is not set in the authentication configuration`);
}
if (this.app.service(service) === undefined) {
throw new Error(`The '${service}' entity service does not exist (set to 'null' if it is not required)`);
}
if (this.app.service(service).id === undefined && entityId === undefined) {
throw new Error(`The '${service}' service does not have an 'id' property and no 'entityId' option is set.`);
}
}
self.hooks({
after: {
create: [ connection('login'), event('login') ],
remove: [ connection('logout'), event('logout') ]
}
});
if (typeof self.publish === 'function') {
self.publish(() => null);
}
}
}