@feathersjs/authentication-client 5.0.0-pre.9 → 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/src/core.ts CHANGED
@@ -1,200 +1,244 @@
1
- import { NotAuthenticated, FeathersError } from '@feathersjs/errors';
2
- import { Application, Params } from '@feathersjs/feathers';
3
- import { AuthenticationRequest, AuthenticationResult } from '@feathersjs/authentication';
4
- import { Storage, StorageWrapper } from './storage';
1
+ import { NotAuthenticated, FeathersError } from '@feathersjs/errors'
2
+ import { Application, Params } from '@feathersjs/feathers'
3
+ import { AuthenticationRequest, AuthenticationResult } from '@feathersjs/authentication'
4
+ import { Storage, StorageWrapper } from './storage'
5
5
 
6
6
  class OauthError extends FeathersError {
7
- constructor (message: string, data?: any) {
8
- super(message, 'OauthError', 401, 'oauth-error', data);
7
+ constructor(message: string, data?: any) {
8
+ super(message, 'OauthError', 401, 'oauth-error', data)
9
9
  }
10
10
  }
11
11
 
12
- const getMatch = (location: Location, key: string): [ string, RegExp ] => {
13
- const regex = new RegExp(`(?:\&?)${key}=([^&]*)`);
14
- const match = location.hash ? location.hash.match(regex) : null;
12
+ const getMatch = (location: Location, key: string): [string, RegExp] => {
13
+ const regex = new RegExp(`(?:\&?)${key}=([^&]*)`)
14
+ const match = location.hash ? location.hash.match(regex) : null
15
15
 
16
16
  if (match !== null) {
17
- const [ , value ] = match;
17
+ const [, value] = match
18
18
 
19
- return [ value, regex ];
19
+ return [value, regex]
20
20
  }
21
21
 
22
- return [ null, regex ];
23
- };
22
+ return [null, regex]
23
+ }
24
24
 
25
- export type ClientConstructor = new (app: Application, options: AuthenticationClientOptions)
26
- => AuthenticationClient;
25
+ export type ClientConstructor = new (
26
+ app: Application,
27
+ options: AuthenticationClientOptions
28
+ ) => AuthenticationClient
27
29
 
28
30
  export interface AuthenticationClientOptions {
29
- storage: Storage;
30
- header: string;
31
- scheme: string;
32
- storageKey: string;
33
- locationKey: string;
34
- locationErrorKey: string;
35
- jwtStrategy: string;
36
- path: string;
37
- Authentication: ClientConstructor;
31
+ storage: Storage
32
+ header: string
33
+ scheme: string
34
+ storageKey: string
35
+ locationKey: string
36
+ locationErrorKey: string
37
+ jwtStrategy: string
38
+ path: string
39
+ Authentication: ClientConstructor
38
40
  }
39
41
 
40
42
  export class AuthenticationClient {
41
- app: Application;
42
- authenticated: boolean;
43
- options: AuthenticationClientOptions;
43
+ app: Application
44
+ authenticated: boolean
45
+ options: AuthenticationClientOptions
44
46
 
45
- constructor (app: Application, options: AuthenticationClientOptions) {
46
- const socket = app.io;
47
- const storage = new StorageWrapper(app.get('storage') || options.storage);
47
+ constructor(app: Application, options: AuthenticationClientOptions) {
48
+ const socket = app.io
49
+ const storage = new StorageWrapper(app.get('storage') || options.storage)
48
50
 
49
- this.app = app;
50
- this.options = options;
51
- this.authenticated = false;
52
- this.app.set('storage', storage);
51
+ this.app = app
52
+ this.options = options
53
+ this.authenticated = false
54
+ this.app.set('storage', storage)
53
55
 
54
56
  if (socket) {
55
- this.handleSocket(socket);
57
+ this.handleSocket(socket)
56
58
  }
57
59
  }
58
60
 
59
- get service () {
60
- return this.app.service(this.options.path);
61
+ get service() {
62
+ return this.app.service(this.options.path)
61
63
  }
62
64
 
63
- get storage () {
64
- return this.app.get('storage') as Storage;
65
+ get storage() {
66
+ return this.app.get('storage') as Storage
65
67
  }
66
68
 
67
- handleSocket (socket: any) {
68
- // Connection events happen on every reconnect
69
- const connected = this.app.io ? 'connect' : 'open';
70
- const disconnected = this.app.io ? 'disconnect' : 'disconnection';
71
-
72
- socket.on(disconnected, () => {
73
- const authPromise = new Promise(resolve =>
74
- socket.once(connected, (data: any) => resolve(data))
75
- )
76
- // Only reconnect when `reAuthenticate()` or `authenticate()`
77
- // has been called explicitly first
78
- // Force reauthentication with the server
79
- .then(() => this.authenticated ? this.reAuthenticate(true) : null);
80
-
81
- this.app.set('authentication', authPromise);
82
- });
69
+ handleSocket(socket: any) {
70
+ // When the socket disconnects and we are still authenticated, try to reauthenticate right away
71
+ // the websocket connection will handle timeouts and retries
72
+ socket.on('disconnect', () => {
73
+ if (this.authenticated) {
74
+ this.reAuthenticate(true)
75
+ }
76
+ })
83
77
  }
84
78
 
85
- getFromLocation (location: Location) {
86
- const [ accessToken, tokenRegex ] = getMatch(location, this.options.locationKey);
79
+ /**
80
+ * Parse the access token or authentication error from the window location hash. Will remove it from the hash
81
+ * if found.
82
+ *
83
+ * @param location The window location
84
+ * @returns The access token if available, will throw an error if found, otherwise null
85
+ */
86
+ getFromLocation(location: Location) {
87
+ const [accessToken, tokenRegex] = getMatch(location, this.options.locationKey)
87
88
 
88
89
  if (accessToken !== null) {
89
- location.hash = location.hash.replace(tokenRegex, '');
90
+ location.hash = location.hash.replace(tokenRegex, '')
90
91
 
91
- return Promise.resolve(accessToken);
92
+ return Promise.resolve(accessToken)
92
93
  }
93
94
 
94
- const [ message, errorRegex ] = getMatch(location, this.options.locationErrorKey);
95
+ const [message, errorRegex] = getMatch(location, this.options.locationErrorKey)
95
96
 
96
97
  if (message !== null) {
97
- location.hash = location.hash.replace(errorRegex, '');
98
+ location.hash = location.hash.replace(errorRegex, '')
98
99
 
99
- return Promise.reject(new OauthError(decodeURIComponent(message)));
100
+ return Promise.reject(new OauthError(decodeURIComponent(message)))
100
101
  }
101
102
 
102
- return Promise.resolve(null);
103
+ return Promise.resolve(null)
103
104
  }
104
105
 
105
- setAccessToken (accessToken: string) {
106
- return this.storage.setItem(this.options.storageKey, accessToken);
106
+ /**
107
+ * Set the access token in storage.
108
+ *
109
+ * @param accessToken The access token to set
110
+ * @returns
111
+ */
112
+ setAccessToken(accessToken: string) {
113
+ return this.storage.setItem(this.options.storageKey, accessToken)
107
114
  }
108
115
 
109
- getAccessToken (): Promise<string|null> {
110
- return this.storage.getItem(this.options.storageKey)
111
- .then((accessToken: string) => {
112
- if (!accessToken && typeof window !== 'undefined' && window.location) {
113
- return this.getFromLocation(window.location);
114
- }
115
-
116
- return accessToken || null;
117
- });
116
+ /**
117
+ * Returns the access token from storage or the window location hash.
118
+ *
119
+ * @returns The access token from storage or location hash
120
+ */
121
+ getAccessToken(): Promise<string | null> {
122
+ return this.storage.getItem(this.options.storageKey).then((accessToken: string) => {
123
+ if (!accessToken && typeof window !== 'undefined' && window.location) {
124
+ return this.getFromLocation(window.location)
125
+ }
126
+
127
+ return accessToken || null
128
+ })
118
129
  }
119
130
 
120
- removeAccessToken () {
121
- return this.storage.removeItem(this.options.storageKey);
131
+ /**
132
+ * Remove the access token from storage
133
+ * @returns The removed access token
134
+ */
135
+ removeAccessToken() {
136
+ return this.storage.removeItem(this.options.storageKey)
122
137
  }
123
138
 
124
- reset () {
125
- this.app.set('authentication', null);
126
- this.authenticated = false;
139
+ /**
140
+ * Reset the internal authentication state. Usually not necessary to call directly.
141
+ *
142
+ * @returns null
143
+ */
144
+ reset() {
145
+ this.app.set('authentication', null)
146
+ this.authenticated = false
127
147
 
128
- return Promise.resolve(null);
148
+ return Promise.resolve(null)
129
149
  }
130
150
 
131
- handleError (error: FeathersError, type: 'authenticate'|'logout') {
132
- if (error.code === 401 || error.code === 403) {
133
- const promise = this.removeAccessToken().then(() => this.reset());
151
+ handleError(error: FeathersError, type: 'authenticate' | 'logout') {
152
+ // For NotAuthenticated, PaymentError, Forbidden, NotFound, MethodNotAllowed, NotAcceptable
153
+ // errors, remove the access token
154
+ if (error.code > 400 && error.code < 408) {
155
+ const promise = this.removeAccessToken().then(() => this.reset())
134
156
 
135
- return type === 'logout' ? promise : promise.then(() => Promise.reject(error));
157
+ return type === 'logout' ? promise : promise.then(() => Promise.reject(error))
136
158
  }
137
159
 
138
- return Promise.reject(error);
160
+ return this.reset().then(() => Promise.reject(error))
139
161
  }
140
162
 
141
- reAuthenticate (force = false, strategy?: string): Promise<AuthenticationResult> {
163
+ /**
164
+ * Try to reauthenticate using the token from storage. Will do nothing if already authenticated unless
165
+ * `force` is true.
166
+ *
167
+ * @param force force reauthentication with the server
168
+ * @param strategy The name of the strategy to use. Defaults to `options.jwtStrategy`
169
+ * @returns The reauthentication result
170
+ */
171
+ reAuthenticate(force = false, strategy?: string): Promise<AuthenticationResult> {
142
172
  // Either returns the authentication state or
143
173
  // tries to re-authenticate with the stored JWT and strategy
144
- const authPromise = this.app.get('authentication');
174
+ let authPromise = this.app.get('authentication')
145
175
 
146
176
  if (!authPromise || force === true) {
147
- return this.getAccessToken().then(accessToken => {
177
+ authPromise = this.getAccessToken().then((accessToken) => {
148
178
  if (!accessToken) {
149
- throw new NotAuthenticated('No accessToken found in storage');
179
+ return this.handleError(new NotAuthenticated('No accessToken found in storage'), 'authenticate')
150
180
  }
151
181
 
152
182
  return this.authenticate({
153
183
  strategy: strategy || this.options.jwtStrategy,
154
184
  accessToken
155
- });
156
- });
185
+ })
186
+ })
187
+ this.app.set('authentication', authPromise)
157
188
  }
158
189
 
159
- return authPromise;
190
+ return authPromise
160
191
  }
161
192
 
162
- authenticate (authentication?: AuthenticationRequest, params?: Params): Promise<AuthenticationResult> {
193
+ /**
194
+ * Authenticate using a specific strategy and data.
195
+ *
196
+ * @param authentication The authentication data
197
+ * @param params Additional parameters
198
+ * @returns The authentication result
199
+ */
200
+ authenticate(authentication?: AuthenticationRequest, params?: Params): Promise<AuthenticationResult> {
163
201
  if (!authentication) {
164
- return this.reAuthenticate();
202
+ return this.reAuthenticate()
165
203
  }
166
204
 
167
- const promise = this.service.create(authentication, params)
205
+ const promise = this.service
206
+ .create(authentication, params)
168
207
  .then((authResult: AuthenticationResult) => {
169
- const { accessToken } = authResult;
208
+ const { accessToken } = authResult
170
209
 
171
- this.authenticated = true;
172
- this.app.emit('login', authResult);
173
- this.app.emit('authenticated', authResult);
210
+ this.authenticated = true
211
+ this.app.emit('login', authResult)
212
+ this.app.emit('authenticated', authResult)
174
213
 
175
- return this.setAccessToken(accessToken).then(() => authResult);
176
- }).catch((error: FeathersError) =>
177
- this.handleError(error, 'authenticate')
178
- );
214
+ return this.setAccessToken(accessToken).then(() => authResult)
215
+ })
216
+ .catch((error: FeathersError) => this.handleError(error, 'authenticate'))
179
217
 
180
- this.app.set('authentication', promise);
218
+ this.app.set('authentication', promise)
181
219
 
182
- return promise;
220
+ return promise
183
221
  }
184
222
 
185
- logout (): Promise<AuthenticationResult | null> {
223
+ /**
224
+ * Log out the current user and remove their token. Will do nothing
225
+ * if not authenticated.
226
+ *
227
+ * @returns The log out result.
228
+ */
229
+ logout(): Promise<AuthenticationResult | null> {
186
230
  return Promise.resolve(this.app.get('authentication'))
187
- .then(() => this.service.remove(null)
188
- .then((authResult: AuthenticationResult) => this.removeAccessToken()
189
- .then(() => this.reset())
190
- .then(() => {
191
- this.app.emit('logout', authResult);
192
-
193
- return authResult;
194
- })
195
- ))
196
- .catch((error: FeathersError) =>
197
- this.handleError(error, 'logout')
198
- );
231
+ .then(() =>
232
+ this.service.remove(null).then((authResult: AuthenticationResult) =>
233
+ this.removeAccessToken()
234
+ .then(() => this.reset())
235
+ .then(() => {
236
+ this.app.emit('logout', authResult)
237
+
238
+ return authResult
239
+ })
240
+ )
241
+ )
242
+ .catch((error: FeathersError) => this.handleError(error, 'logout'))
199
243
  }
200
244
  }
@@ -1,18 +1,26 @@
1
- import { HookContext, NextFunction } from '@feathersjs/feathers';
2
- import { stripSlashes } from '@feathersjs/commons';
1
+ import { HookContext, NextFunction } from '@feathersjs/feathers'
2
+ import { stripSlashes } from '@feathersjs/commons'
3
3
 
4
4
  export const authentication = () => {
5
5
  return (context: HookContext, next: NextFunction) => {
6
- const { app, params, path, method, app: { authentication: service } } = context;
6
+ const {
7
+ app,
8
+ params,
9
+ path,
10
+ method,
11
+ app: { authentication: service }
12
+ } = context
7
13
 
8
14
  if (stripSlashes(service.options.path) === path && method === 'create') {
9
- return next();
15
+ return next()
10
16
  }
11
17
 
12
- return Promise.resolve(app.get('authentication')).then(authResult => {
13
- if (authResult) {
14
- context.params = Object.assign({}, authResult, params);
15
- }
16
- }).then(next);
17
- };
18
- };
18
+ return Promise.resolve(app.get('authentication'))
19
+ .then((authResult) => {
20
+ if (authResult) {
21
+ context.params = Object.assign({}, authResult, params)
22
+ }
23
+ })
24
+ .then(next)
25
+ }
26
+ }
@@ -1,2 +1,2 @@
1
- export { authentication } from './authentication';
2
- export { populateHeader } from './populate-header';
1
+ export { authentication } from './authentication'
2
+ export { populateHeader } from './populate-header'
@@ -1,20 +1,27 @@
1
- import { HookContext, NextFunction } from '@feathersjs/feathers';
1
+ import { HookContext, NextFunction } from '@feathersjs/feathers'
2
2
 
3
3
  export const populateHeader = () => {
4
4
  return (context: HookContext, next: NextFunction) => {
5
- const { app, params: { accessToken } } = context;
6
- const authentication = app.authentication;
5
+ const {
6
+ app,
7
+ params: { accessToken }
8
+ } = context
9
+ const authentication = app.authentication
7
10
 
8
11
  // Set REST header if necessary
9
12
  if (app.rest && accessToken) {
10
- const { scheme, header } = authentication.options;
11
- const authHeader = `${scheme} ${accessToken}`;
13
+ const { scheme, header } = authentication.options
14
+ const authHeader = `${scheme} ${accessToken}`
12
15
 
13
- context.params.headers = Object.assign({}, {
14
- [header]: authHeader
15
- }, context.params.headers);
16
+ context.params.headers = Object.assign(
17
+ {},
18
+ {
19
+ [header]: authHeader
20
+ },
21
+ context.params.headers
22
+ )
16
23
  }
17
24
 
18
- return next();
19
- };
20
- };
25
+ return next()
26
+ }
27
+ }
package/src/index.ts CHANGED
@@ -1,32 +1,37 @@
1
- import { AuthenticationClient, AuthenticationClientOptions } from './core';
2
- import * as hooks from './hooks';
3
- import { Application } from '@feathersjs/feathers';
4
- import { Storage, MemoryStorage, StorageWrapper } from './storage';
1
+ import { AuthenticationClient, AuthenticationClientOptions } from './core'
2
+ import * as hooks from './hooks'
3
+ import { Application } from '@feathersjs/feathers'
4
+ import { Storage, MemoryStorage, StorageWrapper } from './storage'
5
5
 
6
6
  declare module '@feathersjs/feathers/lib/declarations' {
7
- interface Application<ServiceTypes, AppSettings> { // eslint-disable-line
8
- io?: any;
9
- rest?: any;
10
- authentication: AuthenticationClient;
11
- authenticate: AuthenticationClient['authenticate'];
12
- reAuthenticate: AuthenticationClient['reAuthenticate'];
13
- logout: AuthenticationClient['logout'];
7
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
8
+ interface Application<Services, Settings> {
9
+ // eslint-disable-line
10
+ io: any
11
+ rest?: any
12
+ authentication: AuthenticationClient
13
+ authenticate: AuthenticationClient['authenticate']
14
+ reAuthenticate: AuthenticationClient['reAuthenticate']
15
+ logout: AuthenticationClient['logout']
14
16
  }
15
17
  }
16
18
 
17
19
  export const getDefaultStorage = () => {
18
20
  try {
19
- return new StorageWrapper(window.localStorage);
20
- } catch (error) {}
21
+ return new StorageWrapper(window.localStorage)
22
+ } catch (error: any) {}
21
23
 
22
- return new MemoryStorage();
23
- };
24
+ return new MemoryStorage()
25
+ }
24
26
 
25
- export { AuthenticationClient, AuthenticationClientOptions, Storage, MemoryStorage, hooks };
27
+ export { AuthenticationClient, AuthenticationClientOptions, Storage, MemoryStorage, hooks }
26
28
 
27
- export type ClientConstructor = new (app: Application, options: AuthenticationClientOptions) => AuthenticationClient;
29
+ export type ClientConstructor = new (
30
+ app: Application,
31
+ options: AuthenticationClientOptions
32
+ ) => AuthenticationClient
28
33
 
29
- export const defaultStorage: Storage = getDefaultStorage();
34
+ export const defaultStorage: Storage = getDefaultStorage()
30
35
 
31
36
  export const defaults: AuthenticationClientOptions = {
32
37
  header: 'Authorization',
@@ -38,29 +43,26 @@ export const defaults: AuthenticationClientOptions = {
38
43
  path: '/authentication',
39
44
  Authentication: AuthenticationClient,
40
45
  storage: defaultStorage
41
- };
46
+ }
42
47
 
43
48
  const init = (_options: Partial<AuthenticationClientOptions> = {}) => {
44
- const options: AuthenticationClientOptions = Object.assign({}, defaults, _options);
45
- const { Authentication } = options;
49
+ const options: AuthenticationClientOptions = Object.assign({}, defaults, _options)
50
+ const { Authentication } = options
46
51
 
47
52
  return (app: Application) => {
48
- const authentication = new Authentication(app, options);
53
+ const authentication = new Authentication(app, options)
49
54
 
50
- app.authentication = authentication;
51
- app.authenticate = authentication.authenticate.bind(authentication);
52
- app.reAuthenticate = authentication.reAuthenticate.bind(authentication);
53
- app.logout = authentication.logout.bind(authentication);
55
+ app.authentication = authentication
56
+ app.authenticate = authentication.authenticate.bind(authentication)
57
+ app.reAuthenticate = authentication.reAuthenticate.bind(authentication)
58
+ app.logout = authentication.logout.bind(authentication)
54
59
 
55
- app.hooks([
56
- hooks.authentication(),
57
- hooks.populateHeader()
58
- ]);
59
- };
60
- };
60
+ app.hooks([hooks.authentication(), hooks.populateHeader()])
61
+ }
62
+ }
61
63
 
62
- export default init;
64
+ export default init
63
65
 
64
66
  if (typeof module !== 'undefined') {
65
- module.exports = Object.assign(init, module.exports);
67
+ module.exports = Object.assign(init, module.exports)
66
68
  }
package/src/storage.ts CHANGED
@@ -1,49 +1,49 @@
1
1
  export interface Storage {
2
- getItem (key: string): any;
3
- setItem? (key: string, value: any): any;
4
- removeItem? (key: string): any;
2
+ getItem(key: string): any
3
+ setItem?(key: string, value: any): any
4
+ removeItem?(key: string): any
5
5
  }
6
6
 
7
7
  export class MemoryStorage implements Storage {
8
- store: { [key: string]: any };
8
+ store: { [key: string]: any }
9
9
 
10
- constructor () {
11
- this.store = {};
10
+ constructor() {
11
+ this.store = {}
12
12
  }
13
13
 
14
- getItem (key: string) {
15
- return Promise.resolve(this.store[key]);
14
+ getItem(key: string) {
15
+ return Promise.resolve(this.store[key])
16
16
  }
17
17
 
18
- setItem (key: string, value: any) {
19
- return Promise.resolve(this.store[key] = value);
18
+ setItem(key: string, value: any) {
19
+ return Promise.resolve((this.store[key] = value))
20
20
  }
21
21
 
22
- removeItem (key: string) {
23
- const value = this.store[key];
22
+ removeItem(key: string) {
23
+ const value = this.store[key]
24
24
 
25
- delete this.store[key];
25
+ delete this.store[key]
26
26
 
27
- return Promise.resolve(value);
27
+ return Promise.resolve(value)
28
28
  }
29
29
  }
30
30
 
31
31
  export class StorageWrapper implements Storage {
32
- storage: any;
32
+ storage: any
33
33
 
34
- constructor (storage: any) {
35
- this.storage = storage;
34
+ constructor(storage: any) {
35
+ this.storage = storage
36
36
  }
37
37
 
38
- getItem (key: string) {
39
- return Promise.resolve(this.storage.getItem(key));
38
+ getItem(key: string) {
39
+ return Promise.resolve(this.storage.getItem(key))
40
40
  }
41
41
 
42
- setItem (key: string, value: any) {
43
- return Promise.resolve(this.storage.setItem(key, value));
42
+ setItem(key: string, value: any) {
43
+ return Promise.resolve(this.storage.setItem(key, value))
44
44
  }
45
45
 
46
- removeItem (key: string) {
47
- return Promise.resolve(this.storage.removeItem(key));
46
+ removeItem(key: string) {
47
+ return Promise.resolve(this.storage.removeItem(key))
48
48
  }
49
49
  }