@nu-art/user-account-backend 0.400.5
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/SlackReporter.d.ts +8 -0
- package/SlackReporter.js +27 -0
- package/_entity/account/ModuleBE_AccountDB.d.ts +97 -0
- package/_entity/account/ModuleBE_AccountDB.js +333 -0
- package/_entity/account/ModuleBE_SAML.d.ts +37 -0
- package/_entity/account/ModuleBE_SAML.js +92 -0
- package/_entity/account/index.d.ts +3 -0
- package/_entity/account/index.js +3 -0
- package/_entity/account/module-pack.d.ts +3 -0
- package/_entity/account/module-pack.js +7 -0
- package/_entity/failed-login-attempt/ModuleBE_FailedLoginAttemptDB.d.ts +50 -0
- package/_entity/failed-login-attempt/ModuleBE_FailedLoginAttemptDB.js +105 -0
- package/_entity/failed-login-attempt/index.d.ts +2 -0
- package/_entity/failed-login-attempt/index.js +2 -0
- package/_entity/failed-login-attempt/module-pack.d.ts +1 -0
- package/_entity/failed-login-attempt/module-pack.js +3 -0
- package/_entity/login-attempts/ModuleBE_LoginAttemptDB.d.ts +36 -0
- package/_entity/login-attempts/ModuleBE_LoginAttemptDB.js +51 -0
- package/_entity/login-attempts/dispatchers.d.ts +5 -0
- package/_entity/login-attempts/dispatchers.js +2 -0
- package/_entity/login-attempts/index.d.ts +2 -0
- package/_entity/login-attempts/index.js +2 -0
- package/_entity/login-attempts/module-pack.d.ts +1 -0
- package/_entity/login-attempts/module-pack.js +3 -0
- package/_entity/session/ModuleBE_JWT.d.ts +46 -0
- package/_entity/session/ModuleBE_JWT.js +86 -0
- package/_entity/session/ModuleBE_SessionDB.d.ts +77 -0
- package/_entity/session/ModuleBE_SessionDB.js +233 -0
- package/_entity/session/consts.d.ts +22 -0
- package/_entity/session/consts.js +33 -0
- package/_entity/session/index.d.ts +3 -0
- package/_entity/session/index.js +3 -0
- package/_entity/session/module-pack.d.ts +2 -0
- package/_entity/session/module-pack.js +2 -0
- package/_entity.d.ts +8 -0
- package/_entity.js +8 -0
- package/index.d.ts +3 -0
- package/index.js +20 -0
- package/module-pack.d.ts +3 -0
- package/module-pack.js +35 -0
- package/package.json +81 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { DBApiConfigV3, ModuleBE_BaseDB } from '@nu-art/thunderstorm-backend';
|
|
2
|
+
import { DB_FailedLoginAttempt, DBProto_FailedLoginAttempt } from '@nu-art/user-account-shared';
|
|
3
|
+
import { UniqueId } from '@nu-art/ts-common';
|
|
4
|
+
import { OnUserLogin } from '../account/index.js';
|
|
5
|
+
import { SafeDB_Account } from '@nu-art/user-account-shared';
|
|
6
|
+
type Config = DBApiConfigV3<DBProto_FailedLoginAttempt> & {
|
|
7
|
+
loginBlockedTTL: number;
|
|
8
|
+
maxLoginAttempts: number;
|
|
9
|
+
documentTTL: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Manage and handle login failed attempts,
|
|
13
|
+
* Default login blocked timer is 5 minutes
|
|
14
|
+
*/
|
|
15
|
+
export declare class ModuleBE_FailedLoginAttemptDB_Class extends ModuleBE_BaseDB<DBProto_FailedLoginAttempt, Config> implements OnUserLogin {
|
|
16
|
+
constructor();
|
|
17
|
+
__onUserLogin(account: SafeDB_Account): Promise<DB_FailedLoginAttempt | undefined>;
|
|
18
|
+
/**
|
|
19
|
+
* Failed login attempt handler, will take care of updating or managing
|
|
20
|
+
* failed login attempt event.
|
|
21
|
+
* @param accountId The account that failed login.
|
|
22
|
+
*/
|
|
23
|
+
updateFailedLoginAttempt: (accountId: UniqueId) => Promise<[import("@nu-art/ts-common").DB_BaseObject & {
|
|
24
|
+
__metadata1?: any;
|
|
25
|
+
__hardDelete?: boolean;
|
|
26
|
+
__created: number;
|
|
27
|
+
__updated: number;
|
|
28
|
+
_v?: string;
|
|
29
|
+
_originDocId?: UniqueId;
|
|
30
|
+
} & {
|
|
31
|
+
accountId: UniqueId;
|
|
32
|
+
count: number;
|
|
33
|
+
loginSuccessfulAt?: number;
|
|
34
|
+
}, void[]] | undefined>;
|
|
35
|
+
/**
|
|
36
|
+
* After a successful login attempt use this function to validate if user isn't blocked
|
|
37
|
+
* or if block time elapsed
|
|
38
|
+
* @param accountId The account that successfully logged in
|
|
39
|
+
*/
|
|
40
|
+
private onLoginSuccessful;
|
|
41
|
+
private getExistingLoginAttempt;
|
|
42
|
+
private isLoginBlocked;
|
|
43
|
+
private isTTLExpired;
|
|
44
|
+
private incrementLoginAttempt;
|
|
45
|
+
private assertLoginBlock;
|
|
46
|
+
private createNewLoginAttempt;
|
|
47
|
+
private handleBlockedLogin;
|
|
48
|
+
}
|
|
49
|
+
export declare const ModuleBE_FailedLoginAttemptDB: ModuleBE_FailedLoginAttemptDB_Class;
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { ModuleBE_BaseDB } from '@nu-art/thunderstorm-backend';
|
|
2
|
+
import { DBDef_FailedLoginAttempt, DefaultMaxLoginAttempts, ErrorType_LoginBlocked } from '@nu-art/user-account-shared';
|
|
3
|
+
import { ApiException, currentTimeMillis, exists, Format_HHmmss_DDMMYYYY, formatTimestamp, Minute } from '@nu-art/ts-common';
|
|
4
|
+
import { HttpCodes } from '@nu-art/ts-common/core/exceptions/http-codes';
|
|
5
|
+
import { dispatch_OnLoginFailed } from '../login-attempts/dispatchers.js';
|
|
6
|
+
/**
|
|
7
|
+
* Manage and handle login failed attempts,
|
|
8
|
+
* Default login blocked timer is 5 minutes
|
|
9
|
+
*/
|
|
10
|
+
export class ModuleBE_FailedLoginAttemptDB_Class extends ModuleBE_BaseDB {
|
|
11
|
+
constructor() {
|
|
12
|
+
super(DBDef_FailedLoginAttempt);
|
|
13
|
+
this.setDefaultConfig({
|
|
14
|
+
loginBlockedTTL: 5 * Minute, // default login block is 5 minutes
|
|
15
|
+
maxLoginAttempts: DefaultMaxLoginAttempts,
|
|
16
|
+
documentTTL: 3 * Minute
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
__onUserLogin(account) {
|
|
20
|
+
return this.onLoginSuccessful(account._id);
|
|
21
|
+
}
|
|
22
|
+
//######################### Public Logic #########################
|
|
23
|
+
/**
|
|
24
|
+
* Failed login attempt handler, will take care of updating or managing
|
|
25
|
+
* failed login attempt event.
|
|
26
|
+
* @param accountId The account that failed login.
|
|
27
|
+
*/
|
|
28
|
+
updateFailedLoginAttempt = async (accountId) => {
|
|
29
|
+
// Fetch the existing attempt
|
|
30
|
+
const existingLoginAttempt = await this.getExistingLoginAttempt(accountId);
|
|
31
|
+
if (!existingLoginAttempt) {
|
|
32
|
+
return this.createNewLoginAttempt(accountId);
|
|
33
|
+
}
|
|
34
|
+
// Check if the login is already blocked
|
|
35
|
+
if (this.isLoginBlocked(existingLoginAttempt)) {
|
|
36
|
+
return this.handleBlockedLogin(existingLoginAttempt);
|
|
37
|
+
}
|
|
38
|
+
// Check if TTL has expired
|
|
39
|
+
if (this.isTTLExpired(existingLoginAttempt) || exists(existingLoginAttempt.loginSuccessfulAt)) {
|
|
40
|
+
return this.createNewLoginAttempt(accountId);
|
|
41
|
+
}
|
|
42
|
+
// Update the failed login attempt
|
|
43
|
+
return this.incrementLoginAttempt(existingLoginAttempt);
|
|
44
|
+
};
|
|
45
|
+
//######################### Internal Logic #########################
|
|
46
|
+
/**
|
|
47
|
+
* After a successful login attempt use this function to validate if user isn't blocked
|
|
48
|
+
* or if block time elapsed
|
|
49
|
+
* @param accountId The account that successfully logged in
|
|
50
|
+
*/
|
|
51
|
+
onLoginSuccessful = async (accountId) => {
|
|
52
|
+
const loginAttempt = await this.getExistingLoginAttempt(accountId);
|
|
53
|
+
// fail fast if there's no login attempts document
|
|
54
|
+
if (!loginAttempt)
|
|
55
|
+
return;
|
|
56
|
+
// update login timestamp if login is successful and not blocked
|
|
57
|
+
if (loginAttempt.count < this.config.maxLoginAttempts) {
|
|
58
|
+
loginAttempt.loginSuccessfulAt = currentTimeMillis();
|
|
59
|
+
return this.set.item(loginAttempt);
|
|
60
|
+
}
|
|
61
|
+
// if the login is still blocked throw blocked error
|
|
62
|
+
if (this.assertLoginBlock(loginAttempt))
|
|
63
|
+
return this.handleBlockedLogin(loginAttempt);
|
|
64
|
+
};
|
|
65
|
+
// Separate functions to improve logic separation
|
|
66
|
+
getExistingLoginAttempt = async (accountId) => {
|
|
67
|
+
return (await this.query.custom({
|
|
68
|
+
where: { accountId },
|
|
69
|
+
limit: 1,
|
|
70
|
+
orderBy: [{ key: '__created', order: 'desc' }]
|
|
71
|
+
}))[0];
|
|
72
|
+
};
|
|
73
|
+
isLoginBlocked = (loginAttempt) => {
|
|
74
|
+
return loginAttempt.count >= this.config.maxLoginAttempts && this.assertLoginBlock(loginAttempt);
|
|
75
|
+
};
|
|
76
|
+
isTTLExpired = (loginAttempt) => {
|
|
77
|
+
return loginAttempt.__created + this.config.documentTTL < currentTimeMillis();
|
|
78
|
+
};
|
|
79
|
+
incrementLoginAttempt = async (loginAttempt) => {
|
|
80
|
+
loginAttempt.count++;
|
|
81
|
+
const updatedDoc = await this.set.item(loginAttempt);
|
|
82
|
+
await dispatch_OnLoginFailed.dispatchModuleAsync(loginAttempt.accountId);
|
|
83
|
+
if (updatedDoc.count >= this.config.maxLoginAttempts) {
|
|
84
|
+
return this.handleBlockedLogin(updatedDoc);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
assertLoginBlock = (loginAttempt) => {
|
|
88
|
+
const blockedUntil = loginAttempt.__updated + this.config.loginBlockedTTL;
|
|
89
|
+
return currentTimeMillis() < blockedUntil;
|
|
90
|
+
};
|
|
91
|
+
createNewLoginAttempt = async (accountId) => {
|
|
92
|
+
return Promise.all([this.set.item({
|
|
93
|
+
accountId: accountId,
|
|
94
|
+
count: 1
|
|
95
|
+
}), dispatch_OnLoginFailed.dispatchModuleAsync(accountId)]);
|
|
96
|
+
};
|
|
97
|
+
handleBlockedLogin = (blockedLogin) => {
|
|
98
|
+
const loginBlockedUntil = blockedLogin.__updated + this.config.loginBlockedTTL;
|
|
99
|
+
throw new ApiException(HttpCodes._4XX.FORBIDDEN.code, `Login is blocked until ${formatTimestamp(Format_HHmmss_DDMMYYYY, loginBlockedUntil)}`).setErrorBody({
|
|
100
|
+
type: ErrorType_LoginBlocked,
|
|
101
|
+
data: { blockedUntil: loginBlockedUntil }
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export const ModuleBE_FailedLoginAttemptDB = new ModuleBE_FailedLoginAttemptDB_Class();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ModulePackBE_FailedLoginAttemptDB: (import("./ModuleBE_FailedLoginAttemptDB.js").ModuleBE_FailedLoginAttemptDB_Class | import("@nu-art/thunderstorm-backend").ModuleBE_BaseApi_Class<import("@nu-art/user-account-shared").DBProto_FailedLoginAttempt>)[];
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { createApisForDBModuleV3 } from '@nu-art/thunderstorm-backend';
|
|
2
|
+
import { ModuleBE_FailedLoginAttemptDB } from './ModuleBE_FailedLoginAttemptDB.js';
|
|
3
|
+
export const ModulePackBE_FailedLoginAttemptDB = [ModuleBE_FailedLoginAttemptDB, createApisForDBModuleV3(ModuleBE_FailedLoginAttemptDB)];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { DBApiConfigV3, ModuleBE_BaseDB } from '@nu-art/thunderstorm-backend';
|
|
2
|
+
import { DBProto_LoginAttempt } from '@nu-art/user-account-shared';
|
|
3
|
+
import { OnLoginFailed } from './dispatchers.js';
|
|
4
|
+
import { OnUserLogin } from '../account/index.js';
|
|
5
|
+
import { SafeDB_Account } from '@nu-art/user-account-shared';
|
|
6
|
+
type Config = DBApiConfigV3<DBProto_LoginAttempt> & {};
|
|
7
|
+
/**
|
|
8
|
+
* DB entity that collects login metadata and handles blocking of users that failed
|
|
9
|
+
* login credentials over a number of defined times
|
|
10
|
+
*/
|
|
11
|
+
export declare class ModuleBE_LoginAttemptDB_Class extends ModuleBE_BaseDB<DBProto_LoginAttempt, Config> implements OnLoginFailed, OnUserLogin {
|
|
12
|
+
/**
|
|
13
|
+
* Dispatcher that handles failed login events
|
|
14
|
+
* @param accountId The account id that failed login
|
|
15
|
+
*/
|
|
16
|
+
__onLoginFailed(accountId: string): Promise<import("@nu-art/user-account-shared").DB_LoginAttempt>;
|
|
17
|
+
/**
|
|
18
|
+
* On successful event write successful event
|
|
19
|
+
* @param account The logged in account id
|
|
20
|
+
*/
|
|
21
|
+
__onUserLogin(account: SafeDB_Account): Promise<import("@nu-art/user-account-shared").DB_LoginAttempt>;
|
|
22
|
+
constructor();
|
|
23
|
+
/**
|
|
24
|
+
* Default creator function that creates the db document of a login attempt,
|
|
25
|
+
* will be called from the dispatcher listeners that will define the attempt status
|
|
26
|
+
* @param accountId The account that attempted to log-in
|
|
27
|
+
* @param status The login attempts status
|
|
28
|
+
*/
|
|
29
|
+
private createLoginAttempt;
|
|
30
|
+
/**
|
|
31
|
+
* Metadata object generator, for now resolving only device id and tries to resolve id (not always possible)
|
|
32
|
+
*/
|
|
33
|
+
private collectLoginMetadata;
|
|
34
|
+
}
|
|
35
|
+
export declare const ModuleBE_LoginAttemptDB: ModuleBE_LoginAttemptDB_Class;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ModuleBE_BaseDB, } from '@nu-art/thunderstorm-backend';
|
|
2
|
+
import { DBDef_LoginAttempt, LoginStatus_Failed, LoginStatus_Success } from '@nu-art/user-account-shared';
|
|
3
|
+
import { filterKeys } from '@nu-art/ts-common';
|
|
4
|
+
import { MemKey_HttpRequest } from '@nu-art/thunderstorm-backend/modules/server/consts';
|
|
5
|
+
/**
|
|
6
|
+
* DB entity that collects login metadata and handles blocking of users that failed
|
|
7
|
+
* login credentials over a number of defined times
|
|
8
|
+
*/
|
|
9
|
+
export class ModuleBE_LoginAttemptDB_Class extends ModuleBE_BaseDB {
|
|
10
|
+
/**
|
|
11
|
+
* Dispatcher that handles failed login events
|
|
12
|
+
* @param accountId The account id that failed login
|
|
13
|
+
*/
|
|
14
|
+
__onLoginFailed(accountId) {
|
|
15
|
+
return this.createLoginAttempt(accountId, LoginStatus_Failed);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* On successful event write successful event
|
|
19
|
+
* @param account The logged in account id
|
|
20
|
+
*/
|
|
21
|
+
__onUserLogin(account) {
|
|
22
|
+
return this.createLoginAttempt(account._id, LoginStatus_Success);
|
|
23
|
+
}
|
|
24
|
+
constructor() {
|
|
25
|
+
super(DBDef_LoginAttempt);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Default creator function that creates the db document of a login attempt,
|
|
29
|
+
* will be called from the dispatcher listeners that will define the attempt status
|
|
30
|
+
* @param accountId The account that attempted to log-in
|
|
31
|
+
* @param status The login attempts status
|
|
32
|
+
*/
|
|
33
|
+
createLoginAttempt = async (accountId, status) => {
|
|
34
|
+
return this.set.item({
|
|
35
|
+
accountId,
|
|
36
|
+
status,
|
|
37
|
+
metadata: this.collectLoginMetadata()
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Metadata object generator, for now resolving only device id and tries to resolve id (not always possible)
|
|
42
|
+
*/
|
|
43
|
+
collectLoginMetadata = () => {
|
|
44
|
+
const request = MemKey_HttpRequest.get();
|
|
45
|
+
return filterKeys({
|
|
46
|
+
ipAddress: request.headers['x-forwarded-for'] || request.socket.remoteAddress,
|
|
47
|
+
deviceId: request.body.deviceId
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export const ModuleBE_LoginAttemptDB = new ModuleBE_LoginAttemptDB_Class();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ModulePackBE_LoginAttemptDB: (import("./ModuleBE_LoginAttemptDB.js").ModuleBE_LoginAttemptDB_Class | import("@nu-art/thunderstorm-backend").ModuleBE_BaseApi_Class<import("@nu-art/user-account-shared").DBProto_LoginAttempt>)[];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { SecretKey } from '@nu-art/google-services-backend/modules/ModuleBE_SecretManager';
|
|
2
|
+
import { JWT_BaseClaims, Logger, Module, RecursiveObjectOfPrimitives } from '@nu-art/ts-common';
|
|
3
|
+
type HandlerConfig = {
|
|
4
|
+
ttlInMs: number;
|
|
5
|
+
rotationIntervalInMs: number;
|
|
6
|
+
maxSecrets: number;
|
|
7
|
+
};
|
|
8
|
+
type Config = {
|
|
9
|
+
rotationCheckInterval: number;
|
|
10
|
+
default: HandlerConfig;
|
|
11
|
+
};
|
|
12
|
+
export declare class JWT_Handler<T extends RecursiveObjectOfPrimitives> extends Logger {
|
|
13
|
+
private config;
|
|
14
|
+
readonly secret: SecretKey<string[]>;
|
|
15
|
+
private cache?;
|
|
16
|
+
constructor(config: HandlerConfig & {
|
|
17
|
+
label: string;
|
|
18
|
+
secretKey: string;
|
|
19
|
+
projectId?: string;
|
|
20
|
+
});
|
|
21
|
+
create(claims: T, ttlInMs?: number): Promise<string>;
|
|
22
|
+
private getSecret;
|
|
23
|
+
rotateSecret(): Promise<string[]>;
|
|
24
|
+
isActive(jwt: string): Promise<boolean>;
|
|
25
|
+
isExpired(jwt: string): Promise<boolean>;
|
|
26
|
+
verifySignature(jwt: string): Promise<{
|
|
27
|
+
validated: true;
|
|
28
|
+
claims: T & JWT_BaseClaims;
|
|
29
|
+
} | {
|
|
30
|
+
validated: false;
|
|
31
|
+
}>;
|
|
32
|
+
assert(jwt: string): Promise<T & JWT_BaseClaims>;
|
|
33
|
+
}
|
|
34
|
+
export declare class ModuleBE_JWT_Class extends Module<Config> {
|
|
35
|
+
private readonly handlers;
|
|
36
|
+
constructor();
|
|
37
|
+
init(): void;
|
|
38
|
+
jwtHandler<T extends RecursiveObjectOfPrimitives>(jwtConfig: Partial<HandlerConfig> & {
|
|
39
|
+
label: string;
|
|
40
|
+
secretKey: string;
|
|
41
|
+
projectId?: string;
|
|
42
|
+
}): JWT_Handler<T>;
|
|
43
|
+
rotateSecrets(): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
export declare const ModuleBE_JWT: ModuleBE_JWT_Class;
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { SecretKey } from '@nu-art/google-services-backend/modules/ModuleBE_SecretManager';
|
|
2
|
+
import { currentTimeMillis, Day, filterDuplicates, generateHex, Hour, intervalHandler, JwtTools, Logger, merge, Module, MUSTNeverHappenException } from '@nu-art/ts-common';
|
|
3
|
+
export class JWT_Handler extends Logger {
|
|
4
|
+
config;
|
|
5
|
+
secret;
|
|
6
|
+
cache;
|
|
7
|
+
constructor(config) {
|
|
8
|
+
super(config.label);
|
|
9
|
+
this.config = config;
|
|
10
|
+
this.secret = new SecretKey(config.secretKey, config.projectId);
|
|
11
|
+
}
|
|
12
|
+
async create(claims, ttlInMs = this.config.ttlInMs) {
|
|
13
|
+
const secret = await this.getSecret();
|
|
14
|
+
return await JwtTools.encode(claims, secret[0], { expiresIn: Math.floor((currentTimeMillis() + ttlInMs) / 1000) });
|
|
15
|
+
}
|
|
16
|
+
async getSecret() {
|
|
17
|
+
if (this.cache)
|
|
18
|
+
return this.cache;
|
|
19
|
+
return this.cache = await this.secret.get([generateHex(32)]);
|
|
20
|
+
}
|
|
21
|
+
async rotateSecret() {
|
|
22
|
+
delete this.cache;
|
|
23
|
+
let secret = await this.getSecret();
|
|
24
|
+
if (currentTimeMillis() < (await this.secret.modifiedTimestamp()) + this.config.rotationIntervalInMs)
|
|
25
|
+
return secret;
|
|
26
|
+
secret = [generateHex(32), ...secret];
|
|
27
|
+
if (secret.length > this.config.maxSecrets)
|
|
28
|
+
secret.length = this.config.maxSecrets;
|
|
29
|
+
await this.secret.set(secret);
|
|
30
|
+
return secret;
|
|
31
|
+
}
|
|
32
|
+
async isActive(jwt) {
|
|
33
|
+
return await JwtTools.isJwtActive(jwt);
|
|
34
|
+
}
|
|
35
|
+
async isExpired(jwt) {
|
|
36
|
+
return await JwtTools.isJwtExpired(jwt);
|
|
37
|
+
}
|
|
38
|
+
async verifySignature(jwt) {
|
|
39
|
+
jwt = jwt.replace(/^Bearer\s/, '');
|
|
40
|
+
const secrets = await this.getSecret();
|
|
41
|
+
this.logVerbose(`Verifying JWT signature with secrets:`, secrets, jwt);
|
|
42
|
+
for (const secret of secrets) {
|
|
43
|
+
try {
|
|
44
|
+
return { validated: true, claims: await JwtTools.verifySignature(jwt, secret) };
|
|
45
|
+
}
|
|
46
|
+
catch (ignore) {
|
|
47
|
+
this.logError('Error verifying JWT signature', ignore);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { validated: false };
|
|
51
|
+
}
|
|
52
|
+
async assert(jwt) {
|
|
53
|
+
if (await this.isExpired(jwt))
|
|
54
|
+
throw new MUSTNeverHappenException(`JWT is expired: ${jwt}`);
|
|
55
|
+
const result = await this.verifySignature(jwt);
|
|
56
|
+
if (!result.validated)
|
|
57
|
+
throw new MUSTNeverHappenException(`JWT signature is invalid: ${jwt}`);
|
|
58
|
+
return result.claims;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export class ModuleBE_JWT_Class extends Module {
|
|
62
|
+
handlers = [];
|
|
63
|
+
constructor() {
|
|
64
|
+
super();
|
|
65
|
+
this.setDefaultConfig({
|
|
66
|
+
rotationCheckInterval: Hour,
|
|
67
|
+
default: {
|
|
68
|
+
ttlInMs: Hour,
|
|
69
|
+
rotationIntervalInMs: Day,
|
|
70
|
+
maxSecrets: 2,
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
init() {
|
|
75
|
+
intervalHandler(this.rotateSecrets, this.config.rotationCheckInterval);
|
|
76
|
+
}
|
|
77
|
+
jwtHandler(jwtConfig) {
|
|
78
|
+
const jwtHandler = new JWT_Handler(merge(this.config.default, jwtConfig));
|
|
79
|
+
this.handlers.push(jwtHandler);
|
|
80
|
+
return jwtHandler;
|
|
81
|
+
}
|
|
82
|
+
async rotateSecrets() {
|
|
83
|
+
await Promise.all(filterDuplicates(this.handlers, handler => `${handler.secret.secret.projectId}/${handler.secret.secret.key}`).map(handler => handler.rotateSecret()));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export const ModuleBE_JWT = new ModuleBE_JWT_Class();
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { AnyPrimitive, Dispatcher, RecursiveObjectOfPrimitives, TypedKeyValue, UniqueId } from '@nu-art/ts-common';
|
|
2
|
+
import { firestore } from 'firebase-admin';
|
|
3
|
+
import { DBApiConfigV3, ModuleBE_BaseDB } from '@nu-art/thunderstorm-backend';
|
|
4
|
+
import { DB_Session, DBProto_Session } from '@nu-art/user-account-shared';
|
|
5
|
+
import Transaction = firestore.Transaction;
|
|
6
|
+
export type BaseSessionClaims = {
|
|
7
|
+
accountId: string;
|
|
8
|
+
deviceId: string;
|
|
9
|
+
label: string;
|
|
10
|
+
};
|
|
11
|
+
export type Props_CreateSession = {
|
|
12
|
+
linkedSessionId?: string;
|
|
13
|
+
prevSessions?: UniqueId[];
|
|
14
|
+
initialClaims: BaseSessionClaims;
|
|
15
|
+
};
|
|
16
|
+
export interface CollectSessionData<R extends TypedKeyValue<any, AnyPrimitive>> {
|
|
17
|
+
__collectSessionData(data: BaseSessionClaims, transaction?: Transaction): Promise<R>;
|
|
18
|
+
}
|
|
19
|
+
export declare const dispatch_CollectSessionData: Dispatcher<CollectSessionData<TypedKeyValue<any, RecursiveObjectOfPrimitives>>, "__collectSessionData", [data: BaseSessionClaims, transaction?: firestore.Transaction | undefined], TypedKeyValue<any, RecursiveObjectOfPrimitives>>;
|
|
20
|
+
export declare const Const_Default_SessionJWT_SecretKey = "jwt-signer--account-session";
|
|
21
|
+
type Config = DBApiConfigV3<DBProto_Session> & {
|
|
22
|
+
sessionTTLms: number;
|
|
23
|
+
rotationFactor: number;
|
|
24
|
+
maxPrevSession: number;
|
|
25
|
+
jwtSigner: {
|
|
26
|
+
secretKey: string;
|
|
27
|
+
projectId?: string;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
export declare class ModuleBE_SessionDB_Class extends ModuleBE_BaseDB<DBProto_Session, Config> implements CollectSessionData<TypedKeyValue<'session', {
|
|
31
|
+
deviceId: string;
|
|
32
|
+
}>> {
|
|
33
|
+
private jwtHandler;
|
|
34
|
+
constructor();
|
|
35
|
+
init(): void;
|
|
36
|
+
private collectSessionData;
|
|
37
|
+
token: {
|
|
38
|
+
create: (initialClaims: BaseSessionClaims, ttlInMs?: number, transaction?: Transaction) => Promise<string>;
|
|
39
|
+
refresh: (jwtOrigin: string) => Promise<string>;
|
|
40
|
+
verify: (jwt: string) => Promise<BaseSessionClaims & RecursiveObjectOfPrimitives & import("@nu-art/ts-common").JWT_BaseClaims>;
|
|
41
|
+
};
|
|
42
|
+
private __session;
|
|
43
|
+
_session: {
|
|
44
|
+
query: {
|
|
45
|
+
byJwt: (jwt: string, transaction?: Transaction) => Promise<DB_Session>;
|
|
46
|
+
};
|
|
47
|
+
return: (dbSession: DB_Session) => Promise<string>;
|
|
48
|
+
save: (content: Props_CreateSession, jwt: string, transaction?: Transaction) => Promise<DB_Session>;
|
|
49
|
+
create: ((content: Props_CreateSession, ttlInMs?: number, transaction?: Transaction) => Promise<DB_Session>) & {
|
|
50
|
+
andReturn: (content: Props_CreateSession, ttlInMs?: number, transaction?: Transaction) => Promise<string>;
|
|
51
|
+
};
|
|
52
|
+
rotate: {
|
|
53
|
+
refreshIfNeeded: {
|
|
54
|
+
byJwt: (jwt: string, transaction?: Transaction) => Promise<DB_Session | undefined>;
|
|
55
|
+
bySession: (dbSession?: DB_Session, transaction?: Transaction) => Promise<DB_Session>;
|
|
56
|
+
};
|
|
57
|
+
reissue: {
|
|
58
|
+
byJwt: (jwt: string, transaction?: Transaction) => Promise<DB_Session>;
|
|
59
|
+
bySession: (dbSession?: DB_Session, transaction?: Transaction) => Promise<DB_Session>;
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
invalidate: {
|
|
63
|
+
byJwt: (jwt: string, transaction?: Transaction) => Promise<void>;
|
|
64
|
+
bySession: (dbSession?: DB_Session, transaction?: Transaction) => Promise<void>;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
readonly Middleware: () => Promise<void>;
|
|
68
|
+
private locateSession;
|
|
69
|
+
__collectSessionData(data: BaseSessionClaims): Promise<{
|
|
70
|
+
key: "session";
|
|
71
|
+
value: {
|
|
72
|
+
deviceId: string;
|
|
73
|
+
};
|
|
74
|
+
}>;
|
|
75
|
+
}
|
|
76
|
+
export declare const ModuleBE_SessionDB: ModuleBE_SessionDB_Class;
|
|
77
|
+
export {};
|