@nu-art/user-account-backend 0.500.0 → 0.500.6

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.
@@ -1,11 +1,9 @@
1
1
  import { Dispatcher } from '@nu-art/ts-common';
2
2
  import { DB_BaseObject } from '@nu-art/db-api-shared';
3
- import { firestore } from 'firebase-admin';
4
3
  import { ModuleBE_BaseDB } from '@nu-art/db-api-backend';
5
4
  import { FirestoreQuery } from '@nu-art/firebase-shared';
6
5
  import { _SessionKey_Account, AccountEmail, AccountEmailWithDevice, AccountToAssertPassword, AccountToSpice, AccountType, API_UserAccount, DatabaseDef_Account, DB_Account, PasswordAssertionConfig, SafeDB_Account, UI_Account } from '@nu-art/user-account-shared';
7
6
  import { BaseSessionClaims, CollectSessionData } from '../session/ModuleBE_SessionDB.js';
8
- import Transaction = firestore.Transaction;
9
7
  type BaseAccount = {
10
8
  email: string;
11
9
  type: AccountType;
@@ -16,16 +14,19 @@ type SpicedAccount = BaseAccount & {
16
14
  };
17
15
  type AccountToCreate = SpicedAccount | BaseAccount;
18
16
  export interface OnNewUserRegistered {
19
- __onNewUserRegistered(account: SafeDB_Account, transaction: Transaction): void;
17
+ __onNewUserRegistered(account: SafeDB_Account): void;
20
18
  }
21
19
  export interface OnUserLogin {
22
- __onUserLogin(account: SafeDB_Account, transaction: Transaction): void;
20
+ __onUserLogin(account: SafeDB_Account): void;
23
21
  }
24
22
  export interface OnPreLogout {
25
23
  __onPreLogout: () => Promise<void>;
26
24
  }
27
- export declare const dispatch_onAccountLogin: Dispatcher<OnUserLogin, "__onUserLogin", [account: SafeDB_Account, transaction: firestore.Transaction], void>;
25
+ export declare const dispatch_onAccountLogin: Dispatcher<OnUserLogin, "__onUserLogin", [account: SafeDB_Account], void>;
28
26
  export declare const dispatch_onPreLogout: Dispatcher<OnPreLogout, "__onPreLogout", [], void>;
27
+ export interface OnAccountDeleted {
28
+ __onAccountDeleted: (account: SafeDB_Account) => Promise<void>;
29
+ }
29
30
  type Config = {
30
31
  canRegister: boolean;
31
32
  passwordAssertion?: PasswordAssertionConfig;
@@ -46,8 +47,8 @@ export declare class ModuleBE_AccountDB_Class extends ModuleBE_BaseDB<DatabaseDe
46
47
  getSessions(params: API_UserAccount['getSessions']['Params']): Promise<API_UserAccount['getSessions']['Response']>;
47
48
  changeThumbnail(body: API_UserAccount['changeThumbnail']['Body']): Promise<API_UserAccount['changeThumbnail']['Response']>;
48
49
  getPasswordAssertionConfig(_params: API_UserAccount['getPasswordAssertionConfig']['Params']): Promise<API_UserAccount['getPasswordAssertionConfig']['Response']>;
50
+ deleteAccount(params: API_UserAccount['deleteAccount']['Params']): Promise<API_UserAccount['deleteAccount']['Response']>;
49
51
  manipulateQuery(query: FirestoreQuery<DB_Account>): FirestoreQuery<DB_Account>;
50
- canDeleteItems(dbItems: DB_Account[], transaction?: FirebaseFirestore.Transaction): Promise<void>;
51
52
  __collectSessionData(data: BaseSessionClaims): Promise<{
52
53
  key: "account";
53
54
  value: {
@@ -71,22 +72,22 @@ export declare class ModuleBE_AccountDB_Class extends ModuleBE_BaseDB<DatabaseDe
71
72
  saltedPassword?: string | undefined;
72
73
  };
73
74
  }>;
74
- protected preWriteProcessing(dbInstance: UI_Account, originalDbInstance: DatabaseDef_Account['dbType'], transaction?: Transaction): Promise<void>;
75
+ protected preWriteProcessing(dbInstance: UI_Account, originalDbInstance: DatabaseDef_Account['dbType']): Promise<void>;
75
76
  impl: {
76
77
  fixEmail: (objectWithEmail: {
77
78
  email: string;
78
79
  }) => void;
79
80
  assertPasswordCheck: (accountToAssert: AccountToAssertPassword) => void;
80
81
  spiceAccount: (accountToSpice: AccountToSpice) => SpicedAccount;
81
- create: (accountToCreate: AccountToCreate, transaction: Transaction) => Promise<SafeDB_Account>;
82
+ create: (accountToCreate: AccountToCreate) => Promise<SafeDB_Account>;
82
83
  setAccountMemKeys: (account: SafeDB_Account) => Promise<void>;
83
- onAccountCreated: (account: SafeDB_Account, transaction: Transaction) => Promise<void>;
84
- onAccountLogin: (account: SafeDB_Account, transaction: Transaction) => Promise<void>;
85
- queryUnsafeAccount: (credentials: AccountEmail, transaction?: Transaction) => Promise<DB_Account>;
86
- querySafeAccount: (credentials: AccountEmail, transaction?: Transaction) => Promise<SafeDB_Account>;
84
+ onAccountCreated: (account: SafeDB_Account) => Promise<void>;
85
+ onAccountLogin: (account: SafeDB_Account) => Promise<void>;
86
+ queryUnsafeAccount: (credentials: AccountEmail) => Promise<DB_Account>;
87
+ querySafeAccount: (credentials: AccountEmail) => Promise<SafeDB_Account>;
87
88
  };
88
89
  account: {
89
- register: (accountWithPassword: API_UserAccount["registerAccount"]["Body"], transaction?: Transaction) => Promise<API_UserAccount["registerAccount"]["Response"]>;
90
+ register: (accountWithPassword: API_UserAccount["registerAccount"]["Body"]) => Promise<API_UserAccount["registerAccount"]["Response"]>;
90
91
  login: (credentials: API_UserAccount["login"]["Body"]) => Promise<API_UserAccount["login"]["Response"]>;
91
92
  create: (createAccountRequest: API_UserAccount["createAccount"]["Body"]) => Promise<API_UserAccount["createAccount"]["Response"]>;
92
93
  saml: (oAuthAccount: AccountEmailWithDevice) => Promise<import("@nu-art/user-account-shared").DB_Session>;
@@ -97,6 +98,7 @@ export declare class ModuleBE_AccountDB_Class extends ModuleBE_BaseDB<DatabaseDe
97
98
  sessions: import("@nu-art/user-account-shared").DB_Session[];
98
99
  }>;
99
100
  changeThumbnail: (request: API_UserAccount["changeThumbnail"]["Body"]) => Promise<API_UserAccount["changeThumbnail"]["Response"]>;
101
+ delete: (request: API_UserAccount["deleteAccount"]["Params"]) => Promise<API_UserAccount["deleteAccount"]["Response"]>;
100
102
  };
101
103
  password: {
102
104
  assertPasswordExistence: (email: string, password?: string, passwordCheck?: string) => void;
@@ -32,10 +32,9 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
32
32
  if (target) Object.defineProperty(target, contextIn.name, descriptor);
33
33
  done = true;
34
34
  };
35
- import { ApiException, BadImplementationException, cloneObj, compare, dispatch_onApplicationException, Dispatcher, exists, generateHex, hashPasswordWithSalt, md5, MUSTNeverHappenException, Year } from '@nu-art/ts-common';
35
+ import { ApiException, BadImplementationException, cloneObj, compare, dispatch_onApplicationException, Dispatcher, exists, generateHex, hashPasswordWithSalt, md5, Year } from '@nu-art/ts-common';
36
36
  import { ModuleBE_BaseDB } from '@nu-art/db-api-backend';
37
37
  import { ApiHandler } from '@nu-art/http-server';
38
- import { FirestoreInterfaceV3 } from '@nu-art/firebase-backend/firestore-v3/FirestoreInterfaceV3';
39
38
  import { HttpCodes } from '@nu-art/ts-common/core/exceptions/http-codes';
40
39
  import { ApiDef_UserAccount, assertPasswordRules, DBDef_Accounts } from '@nu-art/user-account-shared';
41
40
  import { Header_Authorization, MemKey_AccountEmail, MemKey_AccountId, MemKey_AccountType, MemKey_DB_Session, SessionKey_Account_BE } from '../session/consts.js';
@@ -44,6 +43,7 @@ import { ModuleBE_FailedLoginAttemptDB } from '../failed-login-attempt/ModuleBE_
44
43
  export const dispatch_onAccountLogin = new Dispatcher('__onUserLogin');
45
44
  const dispatch_onAccountRegistered = new Dispatcher('__onNewUserRegistered');
46
45
  export const dispatch_onPreLogout = new Dispatcher('__onPreLogout');
46
+ const dispatch_OnAccountDeleted = new Dispatcher('__onAccountDeleted');
47
47
  let ModuleBE_AccountDB_Class = (() => {
48
48
  let _classSuper = ModuleBE_BaseDB;
49
49
  let _instanceExtraInitializers = [];
@@ -58,6 +58,7 @@ let ModuleBE_AccountDB_Class = (() => {
58
58
  let _getSessions_decorators;
59
59
  let _changeThumbnail_decorators;
60
60
  let _getPasswordAssertionConfig_decorators;
61
+ let _deleteAccount_decorators;
61
62
  return class ModuleBE_AccountDB_Class extends _classSuper {
62
63
  static {
63
64
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
@@ -72,6 +73,7 @@ let ModuleBE_AccountDB_Class = (() => {
72
73
  _getSessions_decorators = [ApiHandler(ApiDef_UserAccount.getSessions)];
73
74
  _changeThumbnail_decorators = [ApiHandler(ApiDef_UserAccount.changeThumbnail)];
74
75
  _getPasswordAssertionConfig_decorators = [ApiHandler(ApiDef_UserAccount.getPasswordAssertionConfig)];
76
+ _deleteAccount_decorators = [ApiHandler(ApiDef_UserAccount.deleteAccount)];
75
77
  __esDecorate(this, null, _refreshSession_decorators, { kind: "method", name: "refreshSession", static: false, private: false, access: { has: obj => "refreshSession" in obj, get: obj => obj.refreshSession }, metadata: _metadata }, null, _instanceExtraInitializers);
76
78
  __esDecorate(this, null, _registerAccount_decorators, { kind: "method", name: "registerAccount", static: false, private: false, access: { has: obj => "registerAccount" in obj, get: obj => obj.registerAccount }, metadata: _metadata }, null, _instanceExtraInitializers);
77
79
  __esDecorate(this, null, _changePassword_decorators, { kind: "method", name: "changePassword", static: false, private: false, access: { has: obj => "changePassword" in obj, get: obj => obj.changePassword }, metadata: _metadata }, null, _instanceExtraInitializers);
@@ -83,6 +85,7 @@ let ModuleBE_AccountDB_Class = (() => {
83
85
  __esDecorate(this, null, _getSessions_decorators, { kind: "method", name: "getSessions", static: false, private: false, access: { has: obj => "getSessions" in obj, get: obj => obj.getSessions }, metadata: _metadata }, null, _instanceExtraInitializers);
84
86
  __esDecorate(this, null, _changeThumbnail_decorators, { kind: "method", name: "changeThumbnail", static: false, private: false, access: { has: obj => "changeThumbnail" in obj, get: obj => obj.changeThumbnail }, metadata: _metadata }, null, _instanceExtraInitializers);
85
87
  __esDecorate(this, null, _getPasswordAssertionConfig_decorators, { kind: "method", name: "getPasswordAssertionConfig", static: false, private: false, access: { has: obj => "getPasswordAssertionConfig" in obj, get: obj => obj.getPasswordAssertionConfig }, metadata: _metadata }, null, _instanceExtraInitializers);
88
+ __esDecorate(this, null, _deleteAccount_decorators, { kind: "method", name: "deleteAccount", static: false, private: false, access: { has: obj => "deleteAccount" in obj, get: obj => obj.deleteAccount }, metadata: _metadata }, null, _instanceExtraInitializers);
86
89
  if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
87
90
  }
88
91
  Middleware = (__runInitializers(this, _instanceExtraInitializers), async () => {
@@ -134,15 +137,18 @@ let ModuleBE_AccountDB_Class = (() => {
134
137
  : this.config.passwordAssertion
135
138
  };
136
139
  }
140
+ async deleteAccount(params) {
141
+ return this.account.delete(params);
142
+ }
137
143
  manipulateQuery(query) {
138
144
  return {
139
145
  ...query,
140
146
  select: ['__created', '_v', '__updated', 'email', '_newPasswordRequired', 'type', '_id', 'thumbnail', 'displayName', '_auditorId', 'description']
141
147
  };
142
148
  }
143
- canDeleteItems(dbItems, transaction) {
144
- throw HttpCodes._5XX.NOT_IMPLEMENTED('Account Deletion is not implemented yet');
145
- }
149
+ // canDeleteItems(dbItems: DB_Account[], transaction?: FirebaseFirestore.Transaction): Promise<void> {
150
+ // throw HttpCodes._5XX.NOT_IMPLEMENTED('Account Deletion is not implemented yet');
151
+ // }
146
152
  async __collectSessionData(data) {
147
153
  const account = await this.query.uniqueAssert(data.accountId);
148
154
  return {
@@ -153,7 +159,7 @@ let ModuleBE_AccountDB_Class = (() => {
153
159
  },
154
160
  };
155
161
  }
156
- async preWriteProcessing(dbInstance, originalDbInstance, transaction) {
162
+ async preWriteProcessing(dbInstance, originalDbInstance) {
157
163
  try {
158
164
  dbInstance._auditorId = MemKey_AccountId.get();
159
165
  }
@@ -178,53 +184,45 @@ let ModuleBE_AccountDB_Class = (() => {
178
184
  saltedPassword: hashPasswordWithSalt(salt, accountToSpice.password)
179
185
  };
180
186
  },
181
- create: async (accountToCreate, transaction) => {
187
+ create: async (accountToCreate) => {
182
188
  let dbAccount = (await this.query.custom({
183
189
  where: { email: accountToCreate.email },
184
190
  limit: 1
185
- }, transaction))[0];
191
+ }))[0];
186
192
  if (dbAccount)
187
193
  throw new ApiException(422, `User with email "${accountToCreate.email}" already exists`);
188
- dbAccount = await this.create.item(accountToCreate, transaction);
194
+ dbAccount = await this.create.item(accountToCreate);
189
195
  return makeAccountSafe(dbAccount);
190
196
  },
191
197
  setAccountMemKeys: async (account) => {
192
198
  MemKey_AccountId.set(account._id);
193
199
  MemKey_AccountEmail.set(account.email);
194
200
  },
195
- onAccountCreated: async (account, transaction) => {
196
- await dispatch_onAccountRegistered.dispatchModuleAsync(account, transaction);
201
+ onAccountCreated: async (account) => {
202
+ await dispatch_onAccountRegistered.dispatchModuleAsync(account);
197
203
  },
198
- onAccountLogin: async (account, transaction) => {
199
- await dispatch_onAccountLogin.dispatchModuleAsync(account, transaction);
204
+ onAccountLogin: async (account) => {
205
+ await dispatch_onAccountLogin.dispatchModuleAsync(account);
200
206
  },
201
- queryUnsafeAccount: async (credentials, transaction) => {
202
- const firestoreQuery = FirestoreInterfaceV3.buildQuery(this.collection, { where: { email: credentials.email } });
203
- let results;
204
- if (transaction)
205
- results = (await transaction.get(firestoreQuery)).docs;
206
- else
207
- results = (await firestoreQuery.get()).docs;
208
- if (results.length !== 1)
209
- if (results.length === 0) {
210
- const apiException = new ApiException(401, `There is no account for email '${credentials.email}'.`);
211
- await dispatch_onApplicationException.dispatchModuleAsync(apiException, this);
212
- throw apiException;
213
- }
214
- else if (results.length > 1) {
215
- this.logWarningBold(`Too many accounts using this email! '${credentials.email}'`);
216
- throw new MUSTNeverHappenException('Too many accounts using this email');
217
- }
218
- return results[0].data();
207
+ queryUnsafeAccount: async (credentials) => {
208
+ const results = await this.query.unManipulatedQuery({ where: { email: credentials.email } });
209
+ if (results.length === 0) {
210
+ const apiException = new ApiException(401, `There is no account for email '${credentials.email}'.`);
211
+ await dispatch_onApplicationException.dispatchModuleAsync(apiException, this);
212
+ throw apiException;
213
+ }
214
+ if (results.length > 1)
215
+ throw new BadImplementationException(`Too many accounts using this email: '${credentials.email}'`);
216
+ return results[0];
219
217
  },
220
- querySafeAccount: async (credentials, transaction) => {
221
- const account = await this.impl.queryUnsafeAccount(credentials, transaction);
218
+ querySafeAccount: async (credentials) => {
219
+ const account = await this.impl.queryUnsafeAccount(credentials);
222
220
  return makeAccountSafe(account);
223
221
  }
224
222
  };
225
223
  account = {
226
224
  // this flow is for creating real human users with email and password
227
- register: async (accountWithPassword, transaction) => {
225
+ register: async (accountWithPassword) => {
228
226
  if (!this.config.canRegister)
229
227
  throw new ApiException(418, 'Registration is disabled!!');
230
228
  this.impl.fixEmail(accountWithPassword);
@@ -233,10 +231,10 @@ let ModuleBE_AccountDB_Class = (() => {
233
231
  email: accountWithPassword.email,
234
232
  password: accountWithPassword.password
235
233
  });
236
- const dbSafeAccount = await this.runTransaction(async (transaction) => {
237
- const dbSafeAccount = await this.impl.create(spicedAccount, transaction);
234
+ const dbSafeAccount = await this.runTransaction(async () => {
235
+ const dbSafeAccount = await this.impl.create(spicedAccount);
238
236
  await this.impl.setAccountMemKeys(dbSafeAccount);
239
- await this.impl.onAccountCreated(dbSafeAccount, transaction);
237
+ await this.impl.onAccountCreated(dbSafeAccount);
240
238
  return dbSafeAccount;
241
239
  });
242
240
  await this.account.login({
@@ -248,14 +246,11 @@ let ModuleBE_AccountDB_Class = (() => {
248
246
  },
249
247
  login: async (credentials) => {
250
248
  this.impl.fixEmail(credentials);
251
- const safeAccount = await this.runTransaction(async (transaction) => {
252
- const dbAccount = await this.impl.queryUnsafeAccount({ email: credentials.email }, transaction);
253
- await this.password.assertPasswordMatch(dbAccount, credentials.password);
254
- const safeAccount = makeAccountSafe(dbAccount);
255
- MemKey_AccountId.set(safeAccount._id);
256
- await this.impl.onAccountLogin(safeAccount, transaction);
257
- return safeAccount;
258
- });
249
+ const dbAccount = await this.impl.queryUnsafeAccount({ email: credentials.email });
250
+ await this.password.assertPasswordMatch(dbAccount, credentials.password);
251
+ const safeAccount = makeAccountSafe(dbAccount);
252
+ MemKey_AccountId.set(safeAccount._id);
253
+ await this.impl.onAccountLogin(safeAccount);
259
254
  const initialClaims = {
260
255
  accountId: safeAccount._id,
261
256
  deviceId: credentials.deviceId,
@@ -268,35 +263,35 @@ let ModuleBE_AccountDB_Class = (() => {
268
263
  const password = createAccountRequest.password;
269
264
  let dbSafeAccount;
270
265
  this.impl.fixEmail(createAccountRequest);
271
- return this.runTransaction(async (transaction) => {
266
+ return this.runTransaction(async () => {
272
267
  if (exists(password) || exists(createAccountRequest.passwordCheck)) {
273
268
  this.impl.assertPasswordCheck(createAccountRequest);
274
269
  const spicedAccount = this.impl.spiceAccount(createAccountRequest);
275
- dbSafeAccount = await this.impl.create(spicedAccount, transaction);
270
+ dbSafeAccount = await this.impl.create(spicedAccount);
276
271
  }
277
272
  else
278
- dbSafeAccount = await this.impl.create(createAccountRequest, transaction);
279
- await this.impl.onAccountCreated(dbSafeAccount, transaction);
273
+ dbSafeAccount = await this.impl.create(createAccountRequest);
274
+ await this.impl.onAccountCreated(dbSafeAccount);
280
275
  return dbSafeAccount;
281
276
  });
282
277
  },
283
278
  saml: async (oAuthAccount) => {
284
279
  this.impl.fixEmail(oAuthAccount);
285
- const dbSafeAccount = await this.runTransaction(async (transaction) => {
280
+ const dbSafeAccount = await this.runTransaction(async () => {
286
281
  let dbSafeAccount;
287
282
  try {
288
- dbSafeAccount = await this.impl.querySafeAccount({ ...oAuthAccount }, transaction);
283
+ dbSafeAccount = await this.impl.querySafeAccount({ ...oAuthAccount });
289
284
  this.logInfo('SAML login account');
290
285
  MemKey_AccountId.set(dbSafeAccount._id);
291
- await this.impl.onAccountLogin(dbSafeAccount, transaction);
286
+ await this.impl.onAccountLogin(dbSafeAccount);
292
287
  }
293
288
  catch (e) {
294
289
  if (e.responseCode !== 401)
295
290
  throw e;
296
291
  this.logInfo('SAML register account');
297
- dbSafeAccount = await this.impl.create({ email: oAuthAccount.email, type: 'user' }, transaction);
292
+ dbSafeAccount = await this.impl.create({ email: oAuthAccount.email, type: 'user' });
298
293
  MemKey_AccountId.set(dbSafeAccount._id);
299
- await this.impl.onAccountCreated(dbSafeAccount, transaction);
294
+ await this.impl.onAccountCreated(dbSafeAccount);
300
295
  }
301
296
  return dbSafeAccount;
302
297
  });
@@ -304,7 +299,7 @@ let ModuleBE_AccountDB_Class = (() => {
304
299
  return ModuleBE_SessionDB._session.create({ initialClaims });
305
300
  },
306
301
  changePassword: async (passwordToChange) => {
307
- return this.runTransaction(async (transaction) => {
302
+ return this.runTransaction(async () => {
308
303
  const email = MemKey_AccountEmail.get();
309
304
  const deviceId = MemKey_DB_Session.get().deviceId;
310
305
  await this.account.login({ email, deviceId, password: passwordToChange.oldPassword }); // perform login to make sure the old password holds
@@ -321,7 +316,7 @@ let ModuleBE_AccountDB_Class = (() => {
321
316
  ...safeAccount,
322
317
  salt: spicedAccount.salt,
323
318
  saltedPassword: spicedAccount.saltedPassword
324
- }, transaction);
319
+ });
325
320
  const initialClaims = {
326
321
  accountId: updatedAccount._id,
327
322
  deviceId,
@@ -332,10 +327,10 @@ let ModuleBE_AccountDB_Class = (() => {
332
327
  });
333
328
  },
334
329
  setPassword: async (passwordBody) => {
335
- return this.runTransaction(async (transaction) => {
330
+ return this.runTransaction(async () => {
336
331
  const email = MemKey_AccountEmail.get();
337
332
  const deviceId = MemKey_DB_Session.get().deviceId;
338
- const dbAccount = await this.impl.queryUnsafeAccount({ email }, transaction);
333
+ const dbAccount = await this.impl.queryUnsafeAccount({ email });
339
334
  if (dbAccount.saltedPassword)
340
335
  throw HttpCodes._4XX.FORBIDDEN('account already has password');
341
336
  const safeAccount = makeAccountSafe(dbAccount);
@@ -345,7 +340,7 @@ let ModuleBE_AccountDB_Class = (() => {
345
340
  ...safeAccount,
346
341
  salt: spicedAccount.salt,
347
342
  saltedPassword: spicedAccount.saltedPassword
348
- }, transaction);
343
+ });
349
344
  const initialClaims = {
350
345
  accountId: updatedAccount._id,
351
346
  deviceId,
@@ -373,6 +368,26 @@ let ModuleBE_AccountDB_Class = (() => {
373
368
  return {
374
369
  account: (await account.get()),
375
370
  };
371
+ },
372
+ delete: async (request) => {
373
+ return await this.runTransaction(async () => {
374
+ const account = await this.query.unique(request.accountId);
375
+ if (!account)
376
+ throw HttpCodes._4XX.NOT_FOUND(`Account with id ${request.accountId} Not Found!`);
377
+ try {
378
+ const safeAccount = makeAccountSafe(account);
379
+ await dispatch_OnAccountDeleted.dispatchModuleAsyncSerial(safeAccount);
380
+ await this.delete.item(account);
381
+ return { account };
382
+ }
383
+ catch (err) {
384
+ const error = err;
385
+ if (error.responseCode === 422)
386
+ throw error;
387
+ this.logError('Failed deleting account', err);
388
+ throw HttpCodes._5XX.INTERNAL_SERVER_ERROR('Failed to delete account', error.message, error);
389
+ }
390
+ });
376
391
  }
377
392
  };
378
393
  password = {
@@ -1,3 +1,3 @@
1
1
  import { Module } from '@nu-art/ts-common';
2
- export declare const ModulePackBE_AccountDB: (import("./ModuleBE_AccountDB.js").ModuleBE_AccountDB_Class | import("@nu-art/db-api-backend").ModuleBE_BaseApi_Class<import("@nu-art/user-account-shared").DatabaseDef_Account>)[];
2
+ export declare const ModulePackBE_AccountDB: (import("./ModuleBE_AccountDB.js").ModuleBE_AccountDB_Class | import("@nu-art/db-api-backend").ModuleBE_BaseApi_Class<import("@nu-art/user-account-shared").DatabaseDef_Account, any>)[];
3
3
  export declare const ModulePackBE_SAML: Module[];
@@ -1 +1 @@
1
- export declare const ModulePackBE_FailedLoginAttemptDB: (import("./ModuleBE_FailedLoginAttemptDB.js").ModuleBE_FailedLoginAttemptDB_Class | import("@nu-art/db-api-backend").ModuleBE_BaseApi_Class<import("@nu-art/user-account-shared").DatabaseDef_FailedLoginAttempt>)[];
1
+ export declare const ModulePackBE_FailedLoginAttemptDB: (import("./ModuleBE_FailedLoginAttemptDB.js").ModuleBE_FailedLoginAttemptDB_Class | import("@nu-art/db-api-backend").ModuleBE_BaseApi_Class<import("@nu-art/user-account-shared").DatabaseDef_FailedLoginAttempt, any>)[];
@@ -1 +1 @@
1
- export declare const ModulePackBE_LoginAttemptDB: (import("./ModuleBE_LoginAttemptDB.js").ModuleBE_LoginAttemptDB_Class | import("@nu-art/db-api-backend").ModuleBE_BaseApi_Class<import("@nu-art/user-account-shared").DatabaseDef_LoginAttempt>)[];
1
+ export declare const ModulePackBE_LoginAttemptDB: (import("./ModuleBE_LoginAttemptDB.js").ModuleBE_LoginAttemptDB_Class | import("@nu-art/db-api-backend").ModuleBE_BaseApi_Class<import("@nu-art/user-account-shared").DatabaseDef_LoginAttempt, any>)[];
@@ -1,8 +1,7 @@
1
1
  import { AnyPrimitive, Dispatcher, RecursiveObjectOfPrimitives, TypedKeyValue } from '@nu-art/ts-common';
2
- import { firestore } from 'firebase-admin';
3
2
  import { ModuleBE_BaseDB } from '@nu-art/db-api-backend';
4
- import { DatabaseDef_Account, DatabaseDef_Session, DB_Session } from '@nu-art/user-account-shared';
5
- import Transaction = firestore.Transaction;
3
+ import { DatabaseDef_Account, DatabaseDef_Session, DB_Session, SafeDB_Account } from '@nu-art/user-account-shared';
4
+ import { OnAccountDeleted } from '../account/ModuleBE_AccountDB.js';
6
5
  export type BaseSessionClaims = {
7
6
  accountId: DatabaseDef_Account['id'];
8
7
  deviceId: string;
@@ -14,9 +13,9 @@ export type Props_CreateSession = {
14
13
  initialClaims: BaseSessionClaims;
15
14
  };
16
15
  export interface CollectSessionData<R extends TypedKeyValue<any, AnyPrimitive>> {
17
- __collectSessionData(data: BaseSessionClaims, transaction?: Transaction): Promise<R>;
16
+ __collectSessionData(data: BaseSessionClaims): Promise<R>;
18
17
  }
19
- export declare const dispatch_CollectSessionData: Dispatcher<CollectSessionData<TypedKeyValue<any, RecursiveObjectOfPrimitives>>, "__collectSessionData", [data: BaseSessionClaims, transaction?: firestore.Transaction | undefined], TypedKeyValue<any, RecursiveObjectOfPrimitives>>;
18
+ export declare const dispatch_CollectSessionData: Dispatcher<CollectSessionData<TypedKeyValue<any, RecursiveObjectOfPrimitives>>, "__collectSessionData", [data: BaseSessionClaims], TypedKeyValue<any, RecursiveObjectOfPrimitives>>;
20
19
  export declare const Const_Default_SessionJWT_SecretKey = "jwt-signer--account-session";
21
20
  type Config = {
22
21
  sessionTTLms: number;
@@ -29,40 +28,41 @@ type Config = {
29
28
  };
30
29
  export declare class ModuleBE_SessionDB_Class extends ModuleBE_BaseDB<DatabaseDef_Session, Config> implements CollectSessionData<TypedKeyValue<'session', {
31
30
  deviceId: string;
32
- }>> {
31
+ }>>, OnAccountDeleted {
33
32
  private jwtHandler;
33
+ __onAccountDeleted: (account: SafeDB_Account) => Promise<void>;
34
34
  constructor();
35
35
  init(): void;
36
36
  private collectSessionData;
37
37
  preWriteProcessing: (instance: DatabaseDef_Session["dbType"]) => Promise<void>;
38
38
  token: {
39
- create: (initialClaims: BaseSessionClaims, ttlInMs?: number, transaction?: Transaction) => Promise<string>;
39
+ create: (initialClaims: BaseSessionClaims, ttlInMs?: number) => Promise<string>;
40
40
  refresh: (jwtOrigin: string) => Promise<string>;
41
41
  verify: (jwt: string) => Promise<BaseSessionClaims & RecursiveObjectOfPrimitives & import("@nu-art/ts-common").JWT_BaseClaims>;
42
42
  };
43
43
  private __session;
44
44
  _session: {
45
45
  query: {
46
- byJwt: (jwt: string, transaction?: Transaction) => Promise<DB_Session>;
46
+ byJwt: (jwt: string) => Promise<DB_Session>;
47
47
  };
48
48
  return: (dbSession: DB_Session) => Promise<string>;
49
- save: (content: Props_CreateSession, jwt: string, transaction?: Transaction) => Promise<DB_Session>;
50
- create: ((content: Props_CreateSession, ttlInMs?: number, transaction?: Transaction) => Promise<DB_Session>) & {
51
- andReturn: (content: Props_CreateSession, ttlInMs?: number, transaction?: Transaction) => Promise<string>;
49
+ save: (content: Props_CreateSession, jwt: string) => Promise<DB_Session>;
50
+ create: ((content: Props_CreateSession, ttlInMs?: number) => Promise<DB_Session>) & {
51
+ andReturn: (content: Props_CreateSession, ttlInMs?: number) => Promise<string>;
52
52
  };
53
53
  rotate: {
54
54
  refreshIfNeeded: {
55
- byJwt: (jwt: string, transaction?: Transaction) => Promise<DB_Session | undefined>;
56
- bySession: (dbSession?: DB_Session, transaction?: Transaction) => Promise<DB_Session>;
55
+ byJwt: (jwt: string) => Promise<DB_Session | undefined>;
56
+ bySession: (dbSession?: DB_Session) => Promise<DB_Session>;
57
57
  };
58
58
  reissue: {
59
- byJwt: (jwt: string, transaction?: Transaction) => Promise<DB_Session>;
60
- bySession: (dbSession?: DB_Session, transaction?: Transaction) => Promise<DB_Session>;
59
+ byJwt: (jwt: string) => Promise<DB_Session>;
60
+ bySession: (dbSession?: DB_Session) => Promise<DB_Session>;
61
61
  };
62
62
  };
63
63
  invalidate: {
64
- byJwt: (jwt: string, transaction?: Transaction) => Promise<void>;
65
- bySession: (dbSession?: DB_Session, transaction?: Transaction) => Promise<void>;
64
+ byJwt: (jwt: string) => Promise<void>;
65
+ bySession: (dbSession?: DB_Session) => Promise<void>;
66
66
  };
67
67
  };
68
68
  readonly Middleware: () => Promise<void>;
@@ -1,4 +1,4 @@
1
- import { ApiException, currentTimeMillis, Day, Dispatcher, filterKeys, isErrorOfType, JwtTools, md5, MUSTNeverHappenException } from '@nu-art/ts-common';
1
+ import { ApiException, currentTimeMillis, Day, Dispatcher, filterKeys, isErrorOfType, JwtTools, MUSTNeverHappenException } from '@nu-art/ts-common';
2
2
  import { ModuleBE_BaseDB } from '@nu-art/db-api-backend';
3
3
  import { AccountType_Service, DBDef_Session } from '@nu-art/user-account-shared';
4
4
  import { Header_Authorization, MemKey_DB_Session, MemKey_Jwt, MemKey_SessionData, SessionKey_Account_BE } from './consts.js';
@@ -8,11 +8,14 @@ import { HttpCodes } from '@nu-art/ts-common/core/exceptions/http-codes';
8
8
  import { _EmptyQuery } from '@nu-art/firebase-shared';
9
9
  import { ModuleBE_AccountDB } from '../account/ModuleBE_AccountDB.js';
10
10
  import { ResponseHeaderKey_JWTToken } from '@nu-art/api-types';
11
- import { dbObjectToId, stringToUniqueId } from '@nu-art/db-api-shared';
11
+ import { dbObjectToId, hashToUniqueId } from '@nu-art/db-api-shared';
12
12
  export const dispatch_CollectSessionData = new Dispatcher('__collectSessionData');
13
13
  export const Const_Default_SessionJWT_SecretKey = 'jwt-signer--account-session';
14
14
  export class ModuleBE_SessionDB_Class extends ModuleBE_BaseDB {
15
15
  jwtHandler;
16
+ __onAccountDeleted = async (account) => {
17
+ await this.delete.where({ accountId: account._id });
18
+ };
16
19
  constructor() {
17
20
  super(DBDef_Session);
18
21
  this.setDefaultConfig({
@@ -33,8 +36,8 @@ export class ModuleBE_SessionDB_Class extends ModuleBE_BaseDB {
33
36
  ...this.config.jwtSigner
34
37
  });
35
38
  }
36
- collectSessionData = async (content, transaction) => {
37
- const collectedData = (await dispatch_CollectSessionData.dispatchModuleAsync(content, transaction));
39
+ collectSessionData = async (content) => {
40
+ const collectedData = (await dispatch_CollectSessionData.dispatchModuleAsync(content));
38
41
  return collectedData.reduce((sessionData, moduleSessionData) => {
39
42
  // We don't skip existing keys. This allows us to override session data provided by infra, with session data provided by app. If wanted, add flag to symbolize this is intentional to all relevant places.
40
43
  sessionData[moduleSessionData.key] = moduleSessionData.value;
@@ -50,8 +53,8 @@ export class ModuleBE_SessionDB_Class extends ModuleBE_BaseDB {
50
53
  delete instance.prevSession;
51
54
  };
52
55
  token = {
53
- create: async (initialClaims, ttlInMs, transaction) => {
54
- const claims = await this.collectSessionData(initialClaims, transaction);
56
+ create: async (initialClaims, ttlInMs) => {
57
+ const claims = await this.collectSessionData(initialClaims);
55
58
  return await this.jwtHandler.create(claims, ttlInMs ?? this.config.sessionTTLms);
56
59
  },
57
60
  refresh: async (jwtOrigin) => {
@@ -83,7 +86,7 @@ export class ModuleBE_SessionDB_Class extends ModuleBE_BaseDB {
83
86
  // Determine if the session should be rotated:
84
87
  return remainingTTL / totalTTL < this.config.rotationFactor;
85
88
  },
86
- refresh: async (dbSession = MemKey_DB_Session.get(), transaction) => {
89
+ refresh: async (dbSession = MemKey_DB_Session.get()) => {
87
90
  this.logInfo(`Refreshing JWT for Account: ${dbSession.accountId}`);
88
91
  const jwt = await this.token.refresh(dbSession.sessionIdJwt);
89
92
  const content = {
@@ -95,9 +98,9 @@ export class ModuleBE_SessionDB_Class extends ModuleBE_BaseDB {
95
98
  label: `refreshed from ${dbSession._id}`,
96
99
  }
97
100
  };
98
- return await this._session.save(content, jwt, transaction);
101
+ return await this._session.save(content, jwt);
99
102
  },
100
- reissue: async (dbSession = MemKey_DB_Session.get(), transaction) => {
103
+ reissue: async (dbSession = MemKey_DB_Session.get()) => {
101
104
  this.logInfo(`Reissuing JWT for Account: ${dbSession.accountId} from Session: ${dbSession._id}`);
102
105
  const claims = await this.token.verify(dbSession.sessionIdJwt);
103
106
  const initialClaims = {
@@ -105,24 +108,24 @@ export class ModuleBE_SessionDB_Class extends ModuleBE_BaseDB {
105
108
  deviceId: claims.deviceId,
106
109
  label: `reissued from ${dbSession._id}`,
107
110
  };
108
- const jwt = await this.token.create(initialClaims, (claims.exp - claims.iat) * 1000, transaction);
111
+ const jwt = await this.token.create(initialClaims, (claims.exp - claims.iat) * 1000);
109
112
  const content = {
110
113
  linkedSessionId: dbSession._id,
111
114
  prevSessions: dbSession.validSessionJwtMd5s,
112
115
  initialClaims: initialClaims
113
116
  };
114
- return await this._session.save(content, jwt, transaction);
117
+ return await this._session.save(content, jwt);
115
118
  }
116
119
  };
117
120
  _session = {
118
121
  query: {
119
- byJwt: async (jwt, transaction) => {
122
+ byJwt: async (jwt) => {
120
123
  return await this.query.uniqueCustom({
121
124
  // We use an md5 to save and query for the session object. The original sessionId(JWT) is too big.
122
- where: { validSessionJwtMd5s: { $ac: stringToUniqueId(md5(jwt)) } },
125
+ where: { validSessionJwtMd5s: { $ac: hashToUniqueId(jwt) } },
123
126
  orderBy: [{ key: '__created', order: 'desc' }],
124
127
  limit: 1
125
- }, transaction);
128
+ });
126
129
  }
127
130
  },
128
131
  return: async (dbSession) => {
@@ -132,8 +135,8 @@ export class ModuleBE_SessionDB_Class extends ModuleBE_BaseDB {
132
135
  MemKey_Jwt.set(jwt);
133
136
  return jwt;
134
137
  },
135
- save: async (content, jwt, transaction) => {
136
- const _id = stringToUniqueId(md5(jwt));
138
+ save: async (content, jwt) => {
139
+ const _id = hashToUniqueId(jwt);
137
140
  const validSessionJwtMd5s = content.prevSessions ? [_id, ...content.prevSessions] : [_id];
138
141
  if (validSessionJwtMd5s.length > this.config.maxPrevSession)
139
142
  validSessionJwtMd5s.length = this.config.maxPrevSession;
@@ -148,56 +151,56 @@ export class ModuleBE_SessionDB_Class extends ModuleBE_BaseDB {
148
151
  }, ['linkedSessionId', 'label']);
149
152
  const idsToDelete = dbSession.validSessionJwtMd5s.slice(1);
150
153
  if (idsToDelete.length)
151
- await this.delete.all(idsToDelete, transaction);
152
- return await this.set.item(dbSession, transaction);
154
+ await this.delete.all(idsToDelete);
155
+ return await this.set.item(dbSession);
153
156
  },
154
- create: Object.assign(async (content, ttlInMs, transaction) => {
157
+ create: Object.assign(async (content, ttlInMs) => {
155
158
  this.logInfo(`Creating JWT for Account: ${content.initialClaims.accountId}`);
156
- const jwt = await this.token.create(content.initialClaims, ttlInMs, transaction);
157
- return await this._session.save(content, jwt, transaction);
159
+ const jwt = await this.token.create(content.initialClaims, ttlInMs);
160
+ return await this._session.save(content, jwt);
158
161
  }, {
159
- andReturn: async (content, ttlInMs, transaction) => {
160
- const dbSession = await this._session.create(content, ttlInMs, transaction);
162
+ andReturn: async (content, ttlInMs) => {
163
+ const dbSession = await this._session.create(content, ttlInMs);
161
164
  return await this._session.return(dbSession);
162
165
  },
163
166
  }),
164
167
  rotate: {
165
168
  refreshIfNeeded: {
166
- byJwt: async (jwt, transaction) => {
169
+ byJwt: async (jwt) => {
167
170
  if (!(await this.__session.shouldRefresh(jwt)))
168
171
  return;
169
- const dbSession = await this._session.query.byJwt(jwt, transaction);
172
+ const dbSession = await this._session.query.byJwt(jwt);
170
173
  this.logInfo(`Refreshing Session by JWT for account(${dbSession.accountId}) sessionId(${dbSession._id})`);
171
- return await this.__session.refresh(dbSession, transaction);
174
+ return await this.__session.refresh(dbSession);
172
175
  },
173
- bySession: async (dbSession = MemKey_DB_Session.get(), transaction) => {
176
+ bySession: async (dbSession = MemKey_DB_Session.get()) => {
174
177
  if (!(await this.__session.shouldRefresh(dbSession.sessionIdJwt)))
175
178
  return dbSession;
176
179
  this.logInfo(`Refreshing Session by dbSession for account(${dbSession.accountId}) sessionId(${dbSession._id})`);
177
- return await this.__session.refresh(dbSession, transaction);
180
+ return await this.__session.refresh(dbSession);
178
181
  }
179
182
  },
180
183
  reissue: {
181
- byJwt: async (jwt, transaction) => {
184
+ byJwt: async (jwt) => {
182
185
  await this.jwtHandler.assert(jwt);
183
- const dbSession = await this._session.query.byJwt(jwt, transaction);
184
- return await this.__session.reissue(dbSession, transaction);
186
+ const dbSession = await this._session.query.byJwt(jwt);
187
+ return await this.__session.reissue(dbSession);
185
188
  },
186
- bySession: async (dbSession = MemKey_DB_Session.get(), transaction) => {
189
+ bySession: async (dbSession = MemKey_DB_Session.get()) => {
187
190
  await this.jwtHandler.assert(dbSession.sessionIdJwt);
188
- return await this.__session.reissue(dbSession, transaction);
191
+ return await this.__session.reissue(dbSession);
189
192
  }
190
193
  }
191
194
  },
192
195
  invalidate: {
193
- byJwt: async (jwt, transaction) => {
194
- const session = await this._session.query.byJwt(jwt, transaction);
195
- return await this._session.invalidate.bySession(session, transaction);
196
+ byJwt: async (jwt) => {
197
+ const session = await this._session.query.byJwt(jwt);
198
+ return await this._session.invalidate.bySession(session);
196
199
  },
197
- bySession: async (dbSession = MemKey_DB_Session.get(), transaction) => {
200
+ bySession: async (dbSession = MemKey_DB_Session.get()) => {
198
201
  if (!dbSession)
199
202
  throw HttpCodes._4XX.UNAUTHORIZED('No session in context to invalidate');
200
- await this.set.item({ ...dbSession, validSessionJwtMd5s: [] }, transaction);
203
+ await this.set.item({ ...dbSession, validSessionJwtMd5s: [] });
201
204
  this.logInfo(`Session invalidated for account: ${dbSession.accountId}, sessionId: ${dbSession._id}`);
202
205
  }
203
206
  }
@@ -214,8 +217,16 @@ export class ModuleBE_SessionDB_Class extends ModuleBE_BaseDB {
214
217
  };
215
218
  async locateSession(jwt) {
216
219
  try {
217
- const { dbSession, claims } = await this.runTransaction(async (t) => {
218
- let dbSession = await this._session.query.byJwt(jwt);
220
+ const { dbSession, claims } = await this.runTransaction(async () => {
221
+ let dbSession;
222
+ try {
223
+ dbSession = await this._session.query.byJwt(jwt);
224
+ }
225
+ catch (err) {
226
+ if (isErrorOfType(err, ApiException)?.responseCode === HttpCodes._4XX.NOT_FOUND.code)
227
+ throw HttpCodes._4XX.UNAUTHORIZED('JWT received in request was not found', err);
228
+ throw err;
229
+ }
219
230
  const latestJwtValidationResult = await this.jwtHandler.verifySignature(dbSession.sessionIdJwt);
220
231
  if (!latestJwtValidationResult.validated)
221
232
  throw new MUSTNeverHappenException(`JWT received from DB is invalid Session id = ${dbSession._id}`);
@@ -262,12 +273,13 @@ export class ModuleBE_SessionDB_Class extends ModuleBE_BaseDB {
262
273
  sessionIdsToDelete.add(session._id);
263
274
  }
264
275
  }
265
- session.validSessionJwtMd5s.forEach(id => {
266
- if (id !== session._id)
267
- sessionIdsToDelete.add(id);
268
- });
269
- if (!session.validSessionJwtMd5s.length)
276
+ if (!session.validSessionJwtMd5s?.length)
270
277
  sessionIdsToDelete.add(session._id);
278
+ else
279
+ session.validSessionJwtMd5s.forEach(id => {
280
+ if (id !== session._id)
281
+ sessionIdsToDelete.add(id);
282
+ });
271
283
  });
272
284
  //Second pass - collect all sessions that are expired or has the old "sessionData" property in their decoded data
273
285
  await Promise.all(sessions.map(async (session) => {
package/module-pack.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import { Module } from '@nu-art/ts-common';
2
- export declare const ModulePackBE_Accounts: Module<any, any, import("@nu-art/ts-common").Validator<any> | import("@nu-art/ts-common").TypeValidator<any>>[];
3
- export declare const ModulePackBE_Accounts_WOSAML: Module<any, any, import("@nu-art/ts-common").Validator<any> | import("@nu-art/ts-common").TypeValidator<any>>[];
2
+ export declare const ModulePackBE_Accounts: Module<any>[];
3
+ export declare const ModulePackBE_Accounts_WOSAML: Module<any>[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nu-art/user-account-backend",
3
- "version": "0.500.0",
3
+ "version": "0.500.6",
4
4
  "description": "User Account Backend",
5
5
  "keywords": [
6
6
  "TacB0sS",
@@ -34,16 +34,16 @@
34
34
  "test": "ts-mocha -w -p src/test/tsconfig.json --timeout 0 --inspect=8107 --watch-files 'src/test/**/*.test.ts' src/test/**/*.test.ts"
35
35
  },
36
36
  "dependencies": {
37
- "@nu-art/db-api-backend": "0.500.0",
38
- "@nu-art/db-api-shared": "0.500.0",
39
- "@nu-art/user-account-shared": "0.500.0",
40
- "@nu-art/firebase-backend": "0.500.0",
41
- "@nu-art/firebase-shared": "0.500.0",
42
- "@nu-art/http-server": "{{THUNDERSTORM_VERSION}}",
43
- "@nu-art/slack-backend": "0.500.0",
44
- "@nu-art/slack-shared": "0.500.0",
45
- "@nu-art/ts-common": "0.500.0",
46
- "@nu-art/api-types": "{{THUNDERSTORM_VERSION}}",
37
+ "@nu-art/db-api-backend": "0.500.6",
38
+ "@nu-art/db-api-shared": "0.500.6",
39
+ "@nu-art/user-account-shared": "0.500.6",
40
+ "@nu-art/firebase-backend": "0.500.6",
41
+ "@nu-art/firebase-shared": "0.500.6",
42
+ "@nu-art/http-server": "0.500.6",
43
+ "@nu-art/slack-backend": "0.500.6",
44
+ "@nu-art/slack-shared": "0.500.6",
45
+ "@nu-art/ts-common": "0.500.6",
46
+ "@nu-art/api-types": "0.500.6",
47
47
  "express": "^4.18.2",
48
48
  "firebase": "^11.9.0",
49
49
  "firebase-admin": "13.4.0",
@@ -55,8 +55,8 @@
55
55
  "xmlbuilder": "^15.1.1"
56
56
  },
57
57
  "devDependencies": {
58
- "@nu-art/testalot": "0.500.0",
59
- "@nu-art/google-services-backend": "0.500.0",
58
+ "@nu-art/testalot": "0.500.6",
59
+ "@nu-art/google-services-backend": "0.500.6",
60
60
  "@types/react": "^18.0.0",
61
61
  "@types/express": "^4.17.17",
62
62
  "@types/history": "^4.7.2",