alp-node-auth 7.2.1 → 8.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/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /* eslint-disable max-lines */
1
2
  import type { IncomingMessage } from 'http';
2
3
  import { promisify } from 'util';
3
4
  import type { Context } from 'alp-node';
@@ -18,8 +19,12 @@ import { AuthenticationService } from './services/authentification/Authenticatio
18
19
  import type { AllowedStrategyKeys } from './services/authentification/types';
19
20
  import UserAccountsService from './services/user/UserAccountsService';
20
21
  import type { AccountService } from './services/user/types';
21
- import { getTokenFromRequest, COOKIE_NAME } from './utils/cookies';
22
- import { createFindConnectedAndUser } from './utils/createFindConnectedAndUser';
22
+ import {
23
+ getTokenFromRequest,
24
+ COOKIE_NAME_TOKEN,
25
+ COOKIE_NAME_STATE,
26
+ } from './utils/cookies';
27
+ import { createFindLoggedInUser } from './utils/createFindLoggedInUser';
23
28
 
24
29
  export { default as MongoUsersManager } from './MongoUsersManager';
25
30
  export { default as UserAccountGoogleService } from './services/user/UserAccountGoogleService';
@@ -31,22 +36,25 @@ export { STATUSES } from './services/user/UserAccountsService';
31
36
  declare module 'alp-types' {
32
37
  // eslint-disable-next-line @typescript-eslint/no-shadow
33
38
  interface ContextState {
34
- connected: NonNullable<ContextState['user']>['_id'] | null | undefined;
35
- user: User | null | undefined;
39
+ loggedInUserId:
40
+ | NonNullable<ContextState['loggedInUser']>['_id']
41
+ | null
42
+ | undefined;
43
+ loggedInUser: User | null | undefined;
36
44
  }
37
45
 
38
46
  interface ContextSanitizedState {
39
- connected:
40
- | NonNullable<ContextSanitizedState['user']>['_id']
47
+ loggedInUserId:
48
+ | NonNullable<ContextSanitizedState['loggedInUser']>['_id']
41
49
  | null
42
50
  | undefined;
43
- user: UserSanitized | null | undefined;
51
+ loggedInUser: UserSanitized | null | undefined;
44
52
  }
45
53
 
46
54
  interface BaseContext {
47
- setConnected: (
48
- connected: NonNullable<ContextState['user']>['_id'],
49
- user: NonNullable<ContextState['user']>,
55
+ setLoggedIn: (
56
+ loggedInUserId: NonNullable<ContextState['loggedInUserId']>,
57
+ loggedInUser: NonNullable<ContextState['loggedInUser']>,
50
58
  ) => Promise<void>;
51
59
  logout: () => void;
52
60
  }
@@ -102,21 +110,21 @@ export default function init<
102
110
  authHooks,
103
111
  });
104
112
 
105
- app.context.setConnected = async function (
113
+ app.context.setLoggedIn = async function (
106
114
  this: Context,
107
- connected: NonNullable<ContextState['user']>['_id'],
108
- user: NonNullable<ContextState['user']>,
115
+ loggedInUserId: NonNullable<ContextState['loggedInUser']>['_id'],
116
+ loggedInUser: NonNullable<ContextState['loggedInUser']>,
109
117
  ): Promise<void> {
110
- logger.debug('setConnected', { connected });
111
- if (!connected) {
112
- throw new Error('Illegal value for setConnected');
118
+ logger.debug('setLoggedIn', { loggedInUser });
119
+ if (!loggedInUserId) {
120
+ throw new Error('Illegal value for setLoggedIn');
113
121
  }
114
122
 
115
- this.state.connected = connected;
116
- this.state.user = user;
123
+ this.state.loggedInUserId = loggedInUserId;
124
+ this.state.loggedInUser = loggedInUser;
117
125
 
118
126
  const token = await signPromisified(
119
- { connected, time: Date.now() },
127
+ { loggedInUserId, time: Date.now() },
120
128
  this.config
121
129
  .get<Map<string, unknown>>('authentication')
122
130
  .get('secretKey'),
@@ -127,20 +135,36 @@ export default function init<
127
135
  },
128
136
  );
129
137
 
138
+ const calcExpiresTime = (): number => {
139
+ const date = new Date();
140
+ date.setDate(date.getDate() + 30);
141
+ return date.getTime();
142
+ };
143
+
130
144
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
131
- this.cookies.set(COOKIE_NAME, token, {
145
+ this.cookies.set(COOKIE_NAME_TOKEN, token, {
132
146
  httpOnly: true,
133
147
  secure: this.config.get('allowHttps'),
134
148
  });
149
+
150
+ this.cookies.set(
151
+ COOKIE_NAME_STATE,
152
+ JSON.stringify({ loggedInUserId, expiresIn: calcExpiresTime() }),
153
+ {
154
+ httpOnly: false,
155
+ secure: this.config.get('allowHttps'),
156
+ },
157
+ );
135
158
  };
136
159
 
137
160
  app.context.logout = function (this: Context): void {
138
- delete this.state.connected;
139
- delete this.state.user;
140
- this.cookies.set(COOKIE_NAME, '', { expires: new Date(1) });
161
+ delete this.state.loggedInUserId;
162
+ delete this.state.loggedInUser;
163
+ this.cookies.set(COOKIE_NAME_TOKEN, '', { expires: new Date(1) });
164
+ this.cookies.set(COOKIE_NAME_STATE, '', { expires: new Date(1) });
141
165
  };
142
166
 
143
- const getConnectedAndUser = createFindConnectedAndUser(
167
+ const findLoggedInUser = createFindLoggedInUser(
144
168
  app.config
145
169
  .get<Map<string, unknown>>('authentication')
146
170
  .get('secretKey') as string,
@@ -150,49 +174,51 @@ export default function init<
150
174
 
151
175
  return {
152
176
  routes: createRoutes(controller),
153
-
154
- getConnectedAndUserFromRequest: (
177
+ findLoggedInUserFromRequest: (
155
178
  req: IncomingMessage,
156
- ): ReturnType<typeof getConnectedAndUser> => {
179
+ ): ReturnType<typeof findLoggedInUser> => {
157
180
  const token = getTokenFromRequest(req);
158
- return getConnectedAndUser(
181
+ return findLoggedInUser(
159
182
  jwtAudience || req.headers['user-agent'],
160
183
  token,
161
184
  );
162
185
  },
163
- getConnectedAndUser,
164
-
186
+ findLoggedInUser,
165
187
  middleware: async <T>(
166
188
  ctx: Context,
167
189
  next: () => T | Promise<T>,
168
190
  ): Promise<T> => {
169
- const token = ctx.cookies.get(COOKIE_NAME);
191
+ const token = ctx.cookies.get(COOKIE_NAME_TOKEN);
170
192
  const userAgent = ctx.request.headers['user-agent'];
171
193
  logger.debug('middleware', { token });
172
194
 
173
195
  const setState = (
174
- connected: U['_id'] | null | undefined,
175
- user: U | null | undefined,
196
+ loggedInUserId: U['_id'] | null | undefined,
197
+ loggedInUser: U | null | undefined,
176
198
  ): void => {
177
- ctx.state.connected = connected;
178
- ctx.state.user = user;
179
- ctx.sanitizedState.connected = connected;
180
- ctx.sanitizedState.user = user && usersManager.sanitize(user);
199
+ ctx.state.loggedInUserId = loggedInUserId;
200
+ ctx.state.user = loggedInUser;
201
+ ctx.sanitizedState.loggedInUserId = loggedInUserId;
202
+ ctx.sanitizedState.loggedInUser =
203
+ loggedInUser && usersManager.sanitize(loggedInUser);
181
204
  };
182
205
 
183
- const [connected, user] = await getConnectedAndUser(
206
+ const [loggedInUserId, loggedInUser] = await findLoggedInUser(
184
207
  jwtAudience || userAgent,
185
208
  token,
186
209
  );
187
- logger.debug('middleware', { connected });
210
+ logger.debug('middleware', { loggedInUserId });
188
211
 
189
- if (connected == null || user == null) {
190
- if (token) ctx.cookies.set(COOKIE_NAME, '', { expires: new Date(1) });
212
+ if (loggedInUserId == null || loggedInUser == null) {
213
+ if (token) {
214
+ ctx.cookies.set(COOKIE_NAME_TOKEN, '', { expires: new Date(1) });
215
+ ctx.cookies.set(COOKIE_NAME_STATE, '', { expires: new Date(1) });
216
+ }
191
217
  setState(null, null);
192
218
  return next();
193
219
  }
194
220
 
195
- setState(connected, user);
221
+ setState(loggedInUserId, loggedInUser);
196
222
  return next();
197
223
  },
198
224
  };
@@ -46,7 +46,7 @@ export type Strategies<StrategyKeys extends AllowedStrategyKeys> = Record<
46
46
  export interface AccessResponseHooks<StrategyKeys, U extends User = User> {
47
47
  afterLoginSuccess?: <StrategyKey extends StrategyKeys>(
48
48
  strategy: StrategyKey,
49
- connectedUser: U,
49
+ loggedInUser: U,
50
50
  ) => void | Promise<void>;
51
51
 
52
52
  afterScopeUpdate?: <StrategyKey extends StrategyKeys>(
@@ -223,9 +223,9 @@ export class AuthenticationService<
223
223
  }
224
224
 
225
225
  async accessResponse<StrategyKey extends StrategyKeys>(
226
- ctx: any,
226
+ ctx: Context,
227
227
  strategy: StrategyKey,
228
- isConnected: undefined | boolean,
228
+ isLoggedIn: boolean,
229
229
  hooks: AccessResponseHooks<StrategyKeys, U>,
230
230
  ): Promise<U> {
231
231
  if (ctx.query.error) {
@@ -250,7 +250,7 @@ export class AuthenticationService<
250
250
  }
251
251
 
252
252
  if (!cookie.isLoginAccess) {
253
- if (!isConnected) {
253
+ if (!isLoggedIn) {
254
254
  throw new Error('You are not connected');
255
255
  }
256
256
  }
@@ -275,9 +275,9 @@ export class AuthenticationService<
275
275
  return user;
276
276
  }
277
277
 
278
- const connectedUser = ctx.state.user;
278
+ const loggedInUser = ctx.state.loggedInUser as U;
279
279
  const { account, user } = await this.userAccountsService.update(
280
- connectedUser,
280
+ loggedInUser,
281
281
  strategy,
282
282
  tokens,
283
283
  cookie.scope,
@@ -288,7 +288,7 @@ export class AuthenticationService<
288
288
  await hooks.afterScopeUpdate(strategy, cookie.scopeKey, account, user);
289
289
  }
290
290
 
291
- return connectedUser;
291
+ return loggedInUser;
292
292
  }
293
293
 
294
294
  refreshAccountTokens(user: U, account: Account): Promise<boolean> {
@@ -2,17 +2,22 @@ import type { IncomingMessage } from 'http';
2
2
  import type { Option } from 'cookies';
3
3
  import Cookies from 'cookies';
4
4
 
5
- export const COOKIE_NAME = 'connectedUser';
5
+ export const COOKIE_NAME_TOKEN = 'loggedInUserToken';
6
+ export const COOKIE_NAME_STATE = 'loggedInUserState';
6
7
 
7
8
  export const getTokenFromRequest = (
8
9
  req: IncomingMessage,
9
10
  options?: Pick<Option, Exclude<keyof Option, 'secure'>>,
10
11
  ): string | undefined => {
12
+ if (req.headers.authorization?.startsWith('Bearer ')) {
13
+ return req.headers.authorization.slice('Bearer '.length);
14
+ }
15
+
11
16
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
12
17
  const cookies = new Cookies(req, null as unknown as any, {
13
18
  ...options,
14
19
  secure: true,
15
20
  });
16
21
 
17
- return cookies.get(COOKIE_NAME);
22
+ return cookies.get(COOKIE_NAME_TOKEN);
18
23
  };
@@ -7,7 +7,7 @@ import type {
7
7
  } from 'jsonwebtoken';
8
8
  import jsonwebtoken from 'jsonwebtoken';
9
9
  import type { Logger } from 'nightingale-logger';
10
- import type { User, UserSanitized } from '../../types.d';
10
+ import type { User, UserSanitized } from '../../types';
11
11
  import type MongoUsersManager from '../MongoUsersManager';
12
12
 
13
13
  type Verify = (
@@ -31,43 +31,42 @@ const createDecodeJWT =
31
31
  algorithms: ['HS512'],
32
32
  audience: jwtAudience,
33
33
  });
34
- return (result as any)?.connected as string | undefined;
34
+ return (result as any)?.loggedInUserId as string | undefined;
35
35
  };
36
36
 
37
- export type FindConnectedAndUser<U extends User> = (
37
+ export type FindLoggedInUser<U extends User> = (
38
38
  jwtAudience?: string,
39
39
  token?: string,
40
40
  ) => Promise<[null | undefined | U['_id'], null | undefined | U]>;
41
41
 
42
- export const createFindConnectedAndUser = <
42
+ export const createFindLoggedInUser = <
43
43
  U extends User,
44
44
  USanitized extends UserSanitized,
45
45
  >(
46
46
  secretKey: string,
47
47
  usersManager: MongoUsersManager<U, USanitized>,
48
48
  logger: Logger,
49
- ): FindConnectedAndUser<U> => {
49
+ ): FindLoggedInUser<U> => {
50
50
  const decodeJwt = createDecodeJWT(secretKey);
51
51
 
52
- const findConnectedAndUser: FindConnectedAndUser<U> = async (
53
- jwtAudience,
54
- token,
55
- ) => {
52
+ const findLoggedInUser: FindLoggedInUser<U> = async (jwtAudience, token) => {
56
53
  if (!token || !jwtAudience) return [null, null];
57
54
 
58
- let connected;
55
+ let loggedInUserId;
59
56
  try {
60
- connected = await decodeJwt(token, jwtAudience);
57
+ loggedInUserId = await decodeJwt(token, jwtAudience);
61
58
  } catch (err: unknown) {
62
59
  logger.debug('failed to verify authentification', { err });
63
60
  }
64
61
 
65
- if (connected == null) return [null, null];
62
+ if (loggedInUserId == null) return [null, null];
66
63
 
67
- const user = await usersManager.findConnected(connected);
64
+ const loggedInUser = await usersManager.findById(loggedInUserId);
68
65
 
69
- return [connected, user];
66
+ if (!loggedInUser) return [null, null];
67
+
68
+ return [loggedInUserId, loggedInUser];
70
69
  };
71
70
 
72
- return findConnectedAndUser;
71
+ return findLoggedInUser;
73
72
  };
@@ -1,6 +0,0 @@
1
- import type { Logger } from 'nightingale-logger';
2
- import type { User, UserSanitized } from '../../types.d';
3
- import type MongoUsersManager from '../MongoUsersManager';
4
- export type FindConnectedAndUser<U extends User> = (jwtAudience?: string, token?: string) => Promise<[null | undefined | U['_id'], null | undefined | U]>;
5
- export declare const createFindConnectedAndUser: <U extends User, USanitized extends UserSanitized>(secretKey: string, usersManager: MongoUsersManager<U, USanitized>, logger: Logger) => FindConnectedAndUser<U>;
6
- //# sourceMappingURL=createFindConnectedAndUser.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"createFindConnectedAndUser.d.ts","sourceRoot":"","sources":["../../../src/utils/createFindConnectedAndUser.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,KAAK,iBAAiB,MAAM,sBAAsB,CAAC;AA0B1D,MAAM,MAAM,oBAAoB,CAAC,CAAC,SAAS,IAAI,IAAI,CACjD,WAAW,CAAC,EAAE,MAAM,EACpB,KAAK,CAAC,EAAE,MAAM,KACX,OAAO,CAAC,CAAC,IAAI,GAAG,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,IAAI,GAAG,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC;AAElE,eAAO,MAAM,0BAA0B,gEAI1B,MAAM,0DAET,MAAM,4BAyBf,CAAC"}