@nu-art/user-account-backend 0.401.8 → 0.401.9
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.
|
@@ -2,7 +2,7 @@ import { DB_BaseObject, Dispatcher } from '@nu-art/ts-common';
|
|
|
2
2
|
import { firestore } from 'firebase-admin';
|
|
3
3
|
import { ModuleBE_BaseDB } from '@nu-art/thunderstorm-backend';
|
|
4
4
|
import { FirestoreQuery } from '@nu-art/firebase-shared';
|
|
5
|
-
import { _SessionKey_Account, Account_ChangePassword, Account_ChangeThumbnail, Account_CreateAccount, Account_Login, Account_RegisterAccount, Account_SetPassword, AccountEmail, AccountEmailWithDevice, AccountToAssertPassword, AccountToSpice, AccountType, DB_Account, DBProto_Account, PasswordAssertionConfig, SafeDB_Account, UI_Account } from '@nu-art/user-account-shared';
|
|
5
|
+
import { _SessionKey_Account, Account_ChangePassword, Account_ChangeThumbnail, Account_CreateAccount, Account_Delete, Account_Login, Account_RegisterAccount, Account_SetPassword, AccountEmail, AccountEmailWithDevice, AccountToAssertPassword, AccountToSpice, AccountType, DB_Account, DBProto_Account, PasswordAssertionConfig, SafeDB_Account, UI_Account } from '@nu-art/user-account-shared';
|
|
6
6
|
import { BaseSessionClaims, CollectSessionData } from '../session/index.js';
|
|
7
7
|
import Transaction = firestore.Transaction;
|
|
8
8
|
type BaseAccount = {
|
|
@@ -25,6 +25,9 @@ export interface OnPreLogout {
|
|
|
25
25
|
}
|
|
26
26
|
export declare const dispatch_onAccountLogin: Dispatcher<OnUserLogin, "__onUserLogin", [account: SafeDB_Account, transaction: firestore.Transaction], void>;
|
|
27
27
|
export declare const dispatch_onPreLogout: Dispatcher<OnPreLogout, "__onPreLogout", [], void>;
|
|
28
|
+
export interface OnAccountDeleted {
|
|
29
|
+
__onAccountDeleted: (account: SafeDB_Account, transaction: Transaction) => Promise<void>;
|
|
30
|
+
}
|
|
28
31
|
type Config = {
|
|
29
32
|
canRegister: boolean;
|
|
30
33
|
passwordAssertion?: PasswordAssertionConfig;
|
|
@@ -35,7 +38,6 @@ export declare class ModuleBE_AccountDB_Class extends ModuleBE_BaseDB<DBProto_Ac
|
|
|
35
38
|
constructor();
|
|
36
39
|
init(): void;
|
|
37
40
|
manipulateQuery(query: FirestoreQuery<DB_Account>): FirestoreQuery<DB_Account>;
|
|
38
|
-
canDeleteItems(dbItems: DB_Account[], transaction?: FirebaseFirestore.Transaction): Promise<void>;
|
|
39
41
|
__collectSessionData(data: BaseSessionClaims): Promise<{
|
|
40
42
|
key: "account";
|
|
41
43
|
value: {
|
|
@@ -84,6 +86,7 @@ export declare class ModuleBE_AccountDB_Class extends ModuleBE_BaseDB<DBProto_Ac
|
|
|
84
86
|
sessions: import("@nu-art/user-account-shared").DB_Session[];
|
|
85
87
|
}>;
|
|
86
88
|
changeThumbnail: (request: Account_ChangeThumbnail["request"]) => Promise<Account_ChangeThumbnail["response"]>;
|
|
89
|
+
delete: (request: Account_Delete["request"]) => Promise<Account_Delete["response"]>;
|
|
87
90
|
};
|
|
88
91
|
password: {
|
|
89
92
|
assertPasswordExistence: (email: string, password?: string, passwordCheck?: string) => void;
|
|
@@ -8,6 +8,7 @@ import { ModuleBE_FailedLoginAttemptDB } from '../failed-login-attempt/index.js'
|
|
|
8
8
|
export const dispatch_onAccountLogin = new Dispatcher('__onUserLogin');
|
|
9
9
|
const dispatch_onAccountRegistered = new Dispatcher('__onNewUserRegistered');
|
|
10
10
|
export const dispatch_onPreLogout = new Dispatcher('__onPreLogout');
|
|
11
|
+
const dispatch_OnAccountDeleted = new Dispatcher('__onAccountDeleted');
|
|
11
12
|
export class ModuleBE_AccountDB_Class extends ModuleBE_BaseDB {
|
|
12
13
|
Middleware = async () => {
|
|
13
14
|
const account = SessionKey_Account_BE.get();
|
|
@@ -33,11 +34,12 @@ export class ModuleBE_AccountDB_Class extends ModuleBE_BaseDB {
|
|
|
33
34
|
createBodyServerApi(ApiDef_Account._v1.setPassword, this.account.setPassword),
|
|
34
35
|
createQueryServerApi(ApiDef_Account._v1.getSessions, this.account.getSessions),
|
|
35
36
|
createBodyServerApi(ApiDef_Account._v1.changeThumbnail, this.account.changeThumbnail),
|
|
37
|
+
createQueryServerApi(ApiDef_Account._v1.deleteAccount, this.account.delete),
|
|
36
38
|
createQueryServerApi(ApiDef_Account._v1.getPasswordAssertionConfig, async () => ({
|
|
37
39
|
config: this.config.ignorePasswordAssertion
|
|
38
40
|
? undefined
|
|
39
41
|
: this.config.passwordAssertion
|
|
40
|
-
}))
|
|
42
|
+
})),
|
|
41
43
|
]);
|
|
42
44
|
}
|
|
43
45
|
manipulateQuery(query) {
|
|
@@ -46,9 +48,9 @@ export class ModuleBE_AccountDB_Class extends ModuleBE_BaseDB {
|
|
|
46
48
|
select: ['__created', '_v', '__updated', 'email', '_newPasswordRequired', 'type', '_id', 'thumbnail', 'displayName', '_auditorId', 'description']
|
|
47
49
|
};
|
|
48
50
|
}
|
|
49
|
-
canDeleteItems(dbItems, transaction) {
|
|
50
|
-
|
|
51
|
-
}
|
|
51
|
+
// canDeleteItems(dbItems: DB_Account[], transaction?: FirebaseFirestore.Transaction): Promise<void> {
|
|
52
|
+
// throw HttpCodes._5XX.NOT_IMPLEMENTED('Account Deletion is not implemented yet');
|
|
53
|
+
// }
|
|
52
54
|
async __collectSessionData(data) {
|
|
53
55
|
const account = await this.query.uniqueAssert(data.accountId);
|
|
54
56
|
return {
|
|
@@ -279,6 +281,26 @@ export class ModuleBE_AccountDB_Class extends ModuleBE_BaseDB {
|
|
|
279
281
|
return {
|
|
280
282
|
account: (await account.get()),
|
|
281
283
|
};
|
|
284
|
+
},
|
|
285
|
+
delete: async (request) => {
|
|
286
|
+
return await this.runTransaction(async (t) => {
|
|
287
|
+
const account = await this.query.unique(request.accountId);
|
|
288
|
+
if (!account)
|
|
289
|
+
throw HttpCodes._4XX.NOT_FOUND(`Account with id ${request.accountId} Not Found!`);
|
|
290
|
+
try {
|
|
291
|
+
const safeAccount = makeAccountSafe(account);
|
|
292
|
+
await dispatch_OnAccountDeleted.dispatchModuleAsyncSerial(safeAccount, t);
|
|
293
|
+
await this.delete.item(account, t);
|
|
294
|
+
return { account };
|
|
295
|
+
}
|
|
296
|
+
catch (err) {
|
|
297
|
+
const error = err;
|
|
298
|
+
if (error.responseCode === 422)
|
|
299
|
+
throw error;
|
|
300
|
+
this.logError('Failed deleting account', err);
|
|
301
|
+
throw HttpCodes._5XX.INTERNAL_SERVER_ERROR('Failed to delete account', error.message, error);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
282
304
|
}
|
|
283
305
|
};
|
|
284
306
|
password = {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { AnyPrimitive, Dispatcher, RecursiveObjectOfPrimitives, TypedKeyValue, UniqueId } from '@nu-art/ts-common';
|
|
2
2
|
import { firestore } from 'firebase-admin';
|
|
3
3
|
import { DBApiConfigV3, ModuleBE_BaseDB } from '@nu-art/thunderstorm-backend';
|
|
4
|
-
import { DB_Session, DBProto_Session } from '@nu-art/user-account-shared';
|
|
4
|
+
import { DB_Session, DBProto_Session, SafeDB_Account } from '@nu-art/user-account-shared';
|
|
5
|
+
import { OnAccountDeleted } from '../account/ModuleBE_AccountDB.js';
|
|
5
6
|
import Transaction = firestore.Transaction;
|
|
6
7
|
export type BaseSessionClaims = {
|
|
7
8
|
accountId: string;
|
|
@@ -29,8 +30,9 @@ type Config = DBApiConfigV3<DBProto_Session> & {
|
|
|
29
30
|
};
|
|
30
31
|
export declare class ModuleBE_SessionDB_Class extends ModuleBE_BaseDB<DBProto_Session, Config> implements CollectSessionData<TypedKeyValue<'session', {
|
|
31
32
|
deviceId: string;
|
|
32
|
-
}
|
|
33
|
+
}>>, OnAccountDeleted {
|
|
33
34
|
private jwtHandler;
|
|
35
|
+
__onAccountDeleted: (account: SafeDB_Account, transaction: Transaction) => Promise<void>;
|
|
34
36
|
constructor();
|
|
35
37
|
init(): void;
|
|
36
38
|
private collectSessionData;
|
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
import { ApiException, currentTimeMillis, Day,
|
|
1
|
+
import { ApiException, currentTimeMillis, Day, dbObjectToId, Dispatcher, filterKeys, isErrorOfType, JwtTools, md5, MUSTNeverHappenException } from '@nu-art/ts-common';
|
|
2
2
|
import { ModuleBE_BaseDB } from '@nu-art/thunderstorm-backend';
|
|
3
|
-
import { DBDef_Session } from '@nu-art/user-account-shared';
|
|
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';
|
|
5
5
|
import { MemKey_HttpResponse } from '@nu-art/thunderstorm-backend/modules/server/consts';
|
|
6
6
|
import { ResponseHeaderKey_JWTToken } from '@nu-art/thunderstorm-shared';
|
|
7
7
|
import { ModuleBE_JWT } from './ModuleBE_JWT.js';
|
|
8
8
|
import { HttpCodes } from '@nu-art/ts-common/core/exceptions/http-codes';
|
|
9
9
|
import { _EmptyQuery } from '@nu-art/firebase-shared';
|
|
10
|
+
import { ModuleBE_AccountDB } from '../account/ModuleBE_AccountDB.js';
|
|
10
11
|
export const dispatch_CollectSessionData = new Dispatcher('__collectSessionData');
|
|
11
12
|
export const Const_Default_SessionJWT_SecretKey = 'jwt-signer--account-session';
|
|
12
13
|
export class ModuleBE_SessionDB_Class extends ModuleBE_BaseDB {
|
|
13
14
|
jwtHandler;
|
|
15
|
+
__onAccountDeleted = async (account, transaction) => {
|
|
16
|
+
await this.delete.where({ accountId: account._id }, transaction);
|
|
17
|
+
};
|
|
14
18
|
constructor() {
|
|
15
19
|
super(DBDef_Session);
|
|
16
20
|
this.setDefaultConfig({
|
|
@@ -144,6 +148,9 @@ export class ModuleBE_SessionDB_Class extends ModuleBE_BaseDB {
|
|
|
144
148
|
label: content.initialClaims.label,
|
|
145
149
|
sessionIdJwt: jwt,
|
|
146
150
|
}, ['linkedSessionId', 'label']);
|
|
151
|
+
const idsToDelete = dbSession.validSessionJwtMd5s.slice(1);
|
|
152
|
+
if (idsToDelete.length)
|
|
153
|
+
await this.delete.all(idsToDelete, transaction);
|
|
147
154
|
return await this.set.item(dbSession, transaction);
|
|
148
155
|
},
|
|
149
156
|
create: Object.assign(async (content, ttlInMs, transaction) => {
|
|
@@ -210,7 +217,15 @@ export class ModuleBE_SessionDB_Class extends ModuleBE_BaseDB {
|
|
|
210
217
|
async locateSession(jwt) {
|
|
211
218
|
try {
|
|
212
219
|
const { dbSession, claims } = await this.runTransaction(async (t) => {
|
|
213
|
-
let dbSession
|
|
220
|
+
let dbSession;
|
|
221
|
+
try {
|
|
222
|
+
dbSession = await this._session.query.byJwt(jwt);
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
if (isErrorOfType(err, ApiException)?.responseCode === HttpCodes._4XX.NOT_FOUND.code)
|
|
226
|
+
throw HttpCodes._4XX.UNAUTHORIZED('JWT received in request was not found', err);
|
|
227
|
+
throw err;
|
|
228
|
+
}
|
|
214
229
|
const latestJwtValidationResult = await this.jwtHandler.verifySignature(dbSession.sessionIdJwt);
|
|
215
230
|
if (!latestJwtValidationResult.validated)
|
|
216
231
|
throw new MUSTNeverHappenException(`JWT received from DB is invalid Session id = ${dbSession._id}`);
|
|
@@ -239,15 +254,46 @@ export class ModuleBE_SessionDB_Class extends ModuleBE_BaseDB {
|
|
|
239
254
|
}
|
|
240
255
|
cleanOldOrExpiredSessions = async () => {
|
|
241
256
|
const sessions = await this.query.custom(_EmptyQuery);
|
|
242
|
-
const
|
|
257
|
+
const accounts = await ModuleBE_AccountDB.query.where({ type: { $neq: AccountType_Service } });
|
|
258
|
+
const validAccountIds = new Set(accounts.map(dbObjectToId));
|
|
259
|
+
const sessionIdsToDelete = new Set();
|
|
260
|
+
const accountSessionMap = {};
|
|
261
|
+
this.logWarning(`#### Cleaning ${sessions.length} sessions for ${validAccountIds.size} accounts ####`);
|
|
262
|
+
//First pass - Collect all sessions that are referenced by newer sessions
|
|
263
|
+
sessions.forEach(session => {
|
|
264
|
+
if (validAccountIds.has(session.accountId)) {
|
|
265
|
+
const currentSession = accountSessionMap[session.accountId];
|
|
266
|
+
if (!currentSession || currentSession.__created < session.__created) {
|
|
267
|
+
accountSessionMap[session.accountId] = session;
|
|
268
|
+
if (currentSession)
|
|
269
|
+
sessionIdsToDelete.add(currentSession._id);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
sessionIdsToDelete.add(session._id);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (!session.validSessionJwtMd5s?.length)
|
|
276
|
+
sessionIdsToDelete.add(session._id);
|
|
277
|
+
else
|
|
278
|
+
session.validSessionJwtMd5s.forEach(id => {
|
|
279
|
+
if (id !== session._id)
|
|
280
|
+
sessionIdsToDelete.add(id);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
//Second pass - collect all sessions that are expired or has the old "sessionData" property in their decoded data
|
|
284
|
+
await Promise.all(sessions.map(async (session) => {
|
|
285
|
+
if (sessionIdsToDelete.has(session._id))
|
|
286
|
+
return;
|
|
243
287
|
const isExpired = await JwtTools.isJwtExpired(session.sessionIdJwt);
|
|
244
288
|
if (isExpired)
|
|
245
|
-
return session;
|
|
289
|
+
return sessionIdsToDelete.add(session._id);
|
|
246
290
|
const decoded = await JwtTools.decode(session.sessionIdJwt);
|
|
247
291
|
if ('sessionData' in decoded)
|
|
248
|
-
return session;
|
|
249
|
-
}))
|
|
250
|
-
|
|
292
|
+
return sessionIdsToDelete.add(session._id);
|
|
293
|
+
}));
|
|
294
|
+
//Delete sessions
|
|
295
|
+
await this.delete.all(Array.from(sessionIdsToDelete));
|
|
296
|
+
this.logWarning(`### Deleted ${sessionIdsToDelete.size} Sessions! ###`);
|
|
251
297
|
};
|
|
252
298
|
}
|
|
253
299
|
export const ModuleBE_SessionDB = new ModuleBE_SessionDB_Class();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nu-art/user-account-backend",
|
|
3
|
-
"version": "0.401.
|
|
3
|
+
"version": "0.401.9",
|
|
4
4
|
"description": "User Account Backend",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"TacB0sS",
|
|
@@ -34,14 +34,14 @@
|
|
|
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/user-account-shared": "0.401.
|
|
38
|
-
"@nu-art/firebase-backend": "0.401.
|
|
39
|
-
"@nu-art/firebase-shared": "0.401.
|
|
40
|
-
"@nu-art/slack-backend": "0.401.
|
|
41
|
-
"@nu-art/slack-shared": "0.401.
|
|
42
|
-
"@nu-art/thunderstorm-backend": "0.401.
|
|
43
|
-
"@nu-art/thunderstorm-shared": "0.401.
|
|
44
|
-
"@nu-art/ts-common": "0.401.
|
|
37
|
+
"@nu-art/user-account-shared": "0.401.9",
|
|
38
|
+
"@nu-art/firebase-backend": "0.401.9",
|
|
39
|
+
"@nu-art/firebase-shared": "0.401.9",
|
|
40
|
+
"@nu-art/slack-backend": "0.401.9",
|
|
41
|
+
"@nu-art/slack-shared": "0.401.9",
|
|
42
|
+
"@nu-art/thunderstorm-backend": "0.401.9",
|
|
43
|
+
"@nu-art/thunderstorm-shared": "0.401.9",
|
|
44
|
+
"@nu-art/ts-common": "0.401.9",
|
|
45
45
|
"express": "^4.18.2",
|
|
46
46
|
"firebase": "^11.9.0",
|
|
47
47
|
"firebase-admin": "13.4.0",
|
|
@@ -53,12 +53,11 @@
|
|
|
53
53
|
"xmlbuilder": "^15.1.1"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@nu-art/testalot": "0.401.
|
|
57
|
-
"@nu-art/google-services-backend": "0.401.
|
|
56
|
+
"@nu-art/testalot": "0.401.9",
|
|
57
|
+
"@nu-art/google-services-backend": "0.401.9",
|
|
58
58
|
"@types/react": "^18.0.0",
|
|
59
59
|
"@types/express": "^4.17.17",
|
|
60
60
|
"@types/history": "^4.7.2",
|
|
61
|
-
"@types/request": "^2.48.1",
|
|
62
61
|
"@types/saml2-js": "^1.6.8",
|
|
63
62
|
"@types/pako": "^2.0.0",
|
|
64
63
|
"@types/jsonwebtoken": "^9.0.6"
|