@feathersjs/authentication-client 5.0.0-pre.10

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 ADDED
@@ -0,0 +1,200 @@
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
+
6
+ class OauthError extends FeathersError {
7
+ constructor (message: string, data?: any) {
8
+ super(message, 'OauthError', 401, 'oauth-error', data);
9
+ }
10
+ }
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;
15
+
16
+ if (match !== null) {
17
+ const [ , value ] = match;
18
+
19
+ return [ value, regex ];
20
+ }
21
+
22
+ return [ null, regex ];
23
+ };
24
+
25
+ export type ClientConstructor = new (app: Application, options: AuthenticationClientOptions)
26
+ => AuthenticationClient;
27
+
28
+ 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;
38
+ }
39
+
40
+ export class AuthenticationClient {
41
+ app: Application;
42
+ authenticated: boolean;
43
+ options: AuthenticationClientOptions;
44
+
45
+ constructor (app: Application, options: AuthenticationClientOptions) {
46
+ const socket = app.io;
47
+ const storage = new StorageWrapper(app.get('storage') || options.storage);
48
+
49
+ this.app = app;
50
+ this.options = options;
51
+ this.authenticated = false;
52
+ this.app.set('storage', storage);
53
+
54
+ if (socket) {
55
+ this.handleSocket(socket);
56
+ }
57
+ }
58
+
59
+ get service () {
60
+ return this.app.service(this.options.path);
61
+ }
62
+
63
+ get storage () {
64
+ return this.app.get('storage') as Storage;
65
+ }
66
+
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
+ });
83
+ }
84
+
85
+ getFromLocation (location: Location) {
86
+ const [ accessToken, tokenRegex ] = getMatch(location, this.options.locationKey);
87
+
88
+ if (accessToken !== null) {
89
+ location.hash = location.hash.replace(tokenRegex, '');
90
+
91
+ return Promise.resolve(accessToken);
92
+ }
93
+
94
+ const [ message, errorRegex ] = getMatch(location, this.options.locationErrorKey);
95
+
96
+ if (message !== null) {
97
+ location.hash = location.hash.replace(errorRegex, '');
98
+
99
+ return Promise.reject(new OauthError(decodeURIComponent(message)));
100
+ }
101
+
102
+ return Promise.resolve(null);
103
+ }
104
+
105
+ setAccessToken (accessToken: string) {
106
+ return this.storage.setItem(this.options.storageKey, accessToken);
107
+ }
108
+
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
+ });
118
+ }
119
+
120
+ removeAccessToken () {
121
+ return this.storage.removeItem(this.options.storageKey);
122
+ }
123
+
124
+ reset () {
125
+ this.app.set('authentication', null);
126
+ this.authenticated = false;
127
+
128
+ return Promise.resolve(null);
129
+ }
130
+
131
+ handleError (error: FeathersError, type: 'authenticate'|'logout') {
132
+ if (error.code === 401 || error.code === 403) {
133
+ const promise = this.removeAccessToken().then(() => this.reset());
134
+
135
+ return type === 'logout' ? promise : promise.then(() => Promise.reject(error));
136
+ }
137
+
138
+ return Promise.reject(error);
139
+ }
140
+
141
+ reAuthenticate (force = false, strategy?: string): Promise<AuthenticationResult> {
142
+ // Either returns the authentication state or
143
+ // tries to re-authenticate with the stored JWT and strategy
144
+ const authPromise = this.app.get('authentication');
145
+
146
+ if (!authPromise || force === true) {
147
+ return this.getAccessToken().then(accessToken => {
148
+ if (!accessToken) {
149
+ throw new NotAuthenticated('No accessToken found in storage');
150
+ }
151
+
152
+ return this.authenticate({
153
+ strategy: strategy || this.options.jwtStrategy,
154
+ accessToken
155
+ });
156
+ });
157
+ }
158
+
159
+ return authPromise;
160
+ }
161
+
162
+ authenticate (authentication?: AuthenticationRequest, params?: Params): Promise<AuthenticationResult> {
163
+ if (!authentication) {
164
+ return this.reAuthenticate();
165
+ }
166
+
167
+ const promise = this.service.create(authentication, params)
168
+ .then((authResult: AuthenticationResult) => {
169
+ const { accessToken } = authResult;
170
+
171
+ this.authenticated = true;
172
+ this.app.emit('login', authResult);
173
+ this.app.emit('authenticated', authResult);
174
+
175
+ return this.setAccessToken(accessToken).then(() => authResult);
176
+ }).catch((error: FeathersError) =>
177
+ this.handleError(error, 'authenticate')
178
+ );
179
+
180
+ this.app.set('authentication', promise);
181
+
182
+ return promise;
183
+ }
184
+
185
+ logout (): Promise<AuthenticationResult | null> {
186
+ 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
+ );
199
+ }
200
+ }
@@ -0,0 +1,18 @@
1
+ import { HookContext, NextFunction } from '@feathersjs/feathers';
2
+ import { stripSlashes } from '@feathersjs/commons';
3
+
4
+ export const authentication = () => {
5
+ return (context: HookContext, next: NextFunction) => {
6
+ const { app, params, path, method, app: { authentication: service } } = context;
7
+
8
+ if (stripSlashes(service.options.path) === path && method === 'create') {
9
+ return next();
10
+ }
11
+
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
+ };
@@ -0,0 +1,2 @@
1
+ export { authentication } from './authentication';
2
+ export { populateHeader } from './populate-header';
@@ -0,0 +1,20 @@
1
+ import { HookContext, NextFunction } from '@feathersjs/feathers';
2
+
3
+ export const populateHeader = () => {
4
+ return (context: HookContext, next: NextFunction) => {
5
+ const { app, params: { accessToken } } = context;
6
+ const authentication = app.authentication;
7
+
8
+ // Set REST header if necessary
9
+ if (app.rest && accessToken) {
10
+ const { scheme, header } = authentication.options;
11
+ const authHeader = `${scheme} ${accessToken}`;
12
+
13
+ context.params.headers = Object.assign({}, {
14
+ [header]: authHeader
15
+ }, context.params.headers);
16
+ }
17
+
18
+ return next();
19
+ };
20
+ };
package/src/index.ts ADDED
@@ -0,0 +1,66 @@
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
+
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'];
14
+ }
15
+ }
16
+
17
+ export const getDefaultStorage = () => {
18
+ try {
19
+ return new StorageWrapper(window.localStorage);
20
+ } catch (error: any) {}
21
+
22
+ return new MemoryStorage();
23
+ };
24
+
25
+ export { AuthenticationClient, AuthenticationClientOptions, Storage, MemoryStorage, hooks };
26
+
27
+ export type ClientConstructor = new (app: Application, options: AuthenticationClientOptions) => AuthenticationClient;
28
+
29
+ export const defaultStorage: Storage = getDefaultStorage();
30
+
31
+ export const defaults: AuthenticationClientOptions = {
32
+ header: 'Authorization',
33
+ scheme: 'Bearer',
34
+ storageKey: 'feathers-jwt',
35
+ locationKey: 'access_token',
36
+ locationErrorKey: 'error',
37
+ jwtStrategy: 'jwt',
38
+ path: '/authentication',
39
+ Authentication: AuthenticationClient,
40
+ storage: defaultStorage
41
+ };
42
+
43
+ const init = (_options: Partial<AuthenticationClientOptions> = {}) => {
44
+ const options: AuthenticationClientOptions = Object.assign({}, defaults, _options);
45
+ const { Authentication } = options;
46
+
47
+ return (app: Application) => {
48
+ const authentication = new Authentication(app, options);
49
+
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);
54
+
55
+ app.hooks([
56
+ hooks.authentication(),
57
+ hooks.populateHeader()
58
+ ]);
59
+ };
60
+ };
61
+
62
+ export default init;
63
+
64
+ if (typeof module !== 'undefined') {
65
+ module.exports = Object.assign(init, module.exports);
66
+ }
package/src/storage.ts ADDED
@@ -0,0 +1,49 @@
1
+ export interface Storage {
2
+ getItem (key: string): any;
3
+ setItem? (key: string, value: any): any;
4
+ removeItem? (key: string): any;
5
+ }
6
+
7
+ export class MemoryStorage implements Storage {
8
+ store: { [key: string]: any };
9
+
10
+ constructor () {
11
+ this.store = {};
12
+ }
13
+
14
+ getItem (key: string) {
15
+ return Promise.resolve(this.store[key]);
16
+ }
17
+
18
+ setItem (key: string, value: any) {
19
+ return Promise.resolve(this.store[key] = value);
20
+ }
21
+
22
+ removeItem (key: string) {
23
+ const value = this.store[key];
24
+
25
+ delete this.store[key];
26
+
27
+ return Promise.resolve(value);
28
+ }
29
+ }
30
+
31
+ export class StorageWrapper implements Storage {
32
+ storage: any;
33
+
34
+ constructor (storage: any) {
35
+ this.storage = storage;
36
+ }
37
+
38
+ getItem (key: string) {
39
+ return Promise.resolve(this.storage.getItem(key));
40
+ }
41
+
42
+ setItem (key: string, value: any) {
43
+ return Promise.resolve(this.storage.setItem(key, value));
44
+ }
45
+
46
+ removeItem (key: string) {
47
+ return Promise.resolve(this.storage.removeItem(key));
48
+ }
49
+ }