@social-mail/social-mail-web-server 1.9.32 → 1.9.33
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/dist/server/model/entities/User.d.ts +1 -0
- package/dist/server/model/entities/User.d.ts.map +1 -1
- package/dist/server/model/entities/User.js +4 -0
- package/dist/server/model/entities/User.js.map +1 -1
- package/dist/server/model/events/LoginSessionEvents.d.ts +1 -0
- package/dist/server/model/events/LoginSessionEvents.d.ts.map +1 -1
- package/dist/server/model/events/LoginSessionEvents.js +11 -0
- package/dist/server/model/events/LoginSessionEvents.js.map +1 -1
- package/dist/server/workflows/SocialWorkflowContext.d.ts.map +1 -1
- package/dist/server/workflows/SocialWorkflowContext.js +2 -0
- package/dist/server/workflows/SocialWorkflowContext.js.map +1 -1
- package/dist/server/workflows/user/login/FailedLoginLockWorkflow.d.ts +13 -0
- package/dist/server/workflows/user/login/FailedLoginLockWorkflow.d.ts.map +1 -0
- package/dist/server/workflows/user/login/FailedLoginLockWorkflow.js +54 -0
- package/dist/server/workflows/user/login/FailedLoginLockWorkflow.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/server/model/entities/User.ts +3 -0
- package/src/server/model/events/LoginSessionEvents.ts +13 -0
- package/src/server/workflows/SocialWorkflowContext.ts +5 -0
- package/src/server/workflows/user/login/FailedLoginLockWorkflow.ts +53 -0
package/package.json
CHANGED
|
@@ -135,6 +135,9 @@ export class User {
|
|
|
135
135
|
@Column({ dataType: "Boolean", default: () => false })
|
|
136
136
|
public isGroup: boolean;
|
|
137
137
|
|
|
138
|
+
@Column({ dataType: "DateTime", nullable: true})
|
|
139
|
+
public loginLockedTill: DateTime;
|
|
140
|
+
|
|
138
141
|
@JsonProperty({ help: "Save this to change password"})
|
|
139
142
|
public changePassword: ChangePassword;
|
|
140
143
|
|
|
@@ -18,6 +18,8 @@ import { AppStringHelper } from "../../../common/AppStringHelper.js";
|
|
|
18
18
|
import { randomUUID } from "node:crypto";
|
|
19
19
|
import RateLimiterService from "../../services/rate-limiter/RateLimiterService.js";
|
|
20
20
|
import ExternalInvoke from "@entity-access/server-pages/dist/decorators/ExternalInvoke.js";
|
|
21
|
+
import WorkflowContext from "@entity-access/entity-access/dist/workflows/WorkflowContext.js";
|
|
22
|
+
import FailedLoginLockWorkflow from "../../workflows/user/login/FailedLoginLockWorkflow.js";
|
|
21
23
|
|
|
22
24
|
export default class LoginSessionEvents extends EntityEvents<LoginSession> {
|
|
23
25
|
|
|
@@ -36,6 +38,9 @@ export default class LoginSessionEvents extends EntityEvents<LoginSession> {
|
|
|
36
38
|
@Inject
|
|
37
39
|
private rateLimiterService: RateLimiterService;
|
|
38
40
|
|
|
41
|
+
@Inject
|
|
42
|
+
private wc: WorkflowContext;
|
|
43
|
+
|
|
39
44
|
@ExternalQuery
|
|
40
45
|
currentUser() {
|
|
41
46
|
const { sessionID = 0, userID = 0 } = this.sessionUser;
|
|
@@ -103,6 +108,9 @@ export default class LoginSessionEvents extends EntityEvents<LoginSession> {
|
|
|
103
108
|
throw new EntityAccessError("Invalid username");
|
|
104
109
|
}
|
|
105
110
|
|
|
111
|
+
if (user.loginLockedTill && DateTime.from(user.loginLockedTill).msSinceEpoch > Date.now()) {
|
|
112
|
+
throw new EntityAccessError(`Too many failed login attempts try after some time`);
|
|
113
|
+
}
|
|
106
114
|
|
|
107
115
|
const { checkPassword, passwordIsAccessToken } = entity;
|
|
108
116
|
const now = DateTime.now;
|
|
@@ -216,6 +224,9 @@ export default class LoginSessionEvents extends EntityEvents<LoginSession> {
|
|
|
216
224
|
async afterInsert(entity: LoginSession, entry: ChangeEntry) {
|
|
217
225
|
|
|
218
226
|
if (entity.status !== "created") {
|
|
227
|
+
|
|
228
|
+
await FailedLoginLockWorkflow.queue(this.wc, entity);
|
|
229
|
+
|
|
219
230
|
await this.sessionUser.setAuthCookie({
|
|
220
231
|
userID: entity.userID,
|
|
221
232
|
sessionID: entity.sessionID,
|
|
@@ -350,6 +361,8 @@ export default class LoginSessionEvents extends EntityEvents<LoginSession> {
|
|
|
350
361
|
return;
|
|
351
362
|
}
|
|
352
363
|
|
|
364
|
+
await FailedLoginLockWorkflow.cancel(this.wc, entity);
|
|
365
|
+
|
|
353
366
|
await this.sessionUser.setAuthCookie({
|
|
354
367
|
userID: entity.userID,
|
|
355
368
|
sessionID: entity.sessionID,
|
|
@@ -57,6 +57,7 @@ import { AppWorkflowContext } from "./AppWorkflowContext.js";
|
|
|
57
57
|
import DeleteFileHistoryWorkflow from "./files/DeleteFileHistoryWorkflow.js";
|
|
58
58
|
import IndexEmailTextContentWorkflow from "./email/index/IndexEmailTextContentWorkflow.js";
|
|
59
59
|
import IndexEmailAddressWorkflow from "./email/index/IndexEmailAddressWorkflow.js";
|
|
60
|
+
import FailedLoginLockWorkflow from "./user/login/FailedLoginLockWorkflow.js";
|
|
60
61
|
|
|
61
62
|
@RegisterSingleton
|
|
62
63
|
export default class SocialWorkflowContext extends AppWorkflowContext {
|
|
@@ -100,6 +101,10 @@ export default class SocialWorkflowContext extends AppWorkflowContext {
|
|
|
100
101
|
// this.register(MigrateEmailFilesWorkflow);
|
|
101
102
|
this.register(CleanupDeletedFilesWorkflow);
|
|
102
103
|
|
|
104
|
+
|
|
105
|
+
this.register(FailedLoginLockWorkflow);
|
|
106
|
+
|
|
107
|
+
|
|
103
108
|
this.register(PendingPostReceiveWorkflow);
|
|
104
109
|
this.register(PendingReferencesWorkflow);
|
|
105
110
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import TimeSpan from "@entity-access/entity-access/dist/types/TimeSpan.js";
|
|
2
|
+
import Workflow, { UniqueActivity } from "@entity-access/entity-access/dist/workflows/Workflow.js";
|
|
3
|
+
import SocialMailContext from "../../../model/SocialMailContext.js";
|
|
4
|
+
import Inject from "@entity-access/entity-access/dist/di/di.js";
|
|
5
|
+
import Sql from "@entity-access/entity-access/dist/sql/Sql.js";
|
|
6
|
+
import DateTime from "@entity-access/entity-access/dist/types/DateTime.js";
|
|
7
|
+
import WorkflowContext from "@entity-access/entity-access/dist/workflows/WorkflowContext.js";
|
|
8
|
+
import type LoginSession from "../../../model/entities/LoginSession.js";
|
|
9
|
+
|
|
10
|
+
export default class FailedLoginLockWorkflow extends Workflow<{ userID }> {
|
|
11
|
+
|
|
12
|
+
static async queue(wc: WorkflowContext, { userID, sessionID }: LoginSession) {
|
|
13
|
+
return wc.queue(FailedLoginLockWorkflow, { userID }, { id: `track-failed-login-${sessionID}`});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static async cancel(wc: WorkflowContext, { sessionID }: LoginSession) {
|
|
17
|
+
return wc.storage.delete(`track-failed-login-${sessionID}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async run() {
|
|
21
|
+
this.preserveTime = TimeSpan.fromMinutes(1);
|
|
22
|
+
await this.delay(TimeSpan.fromMinutes(5));
|
|
23
|
+
|
|
24
|
+
await this.lockUser();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@UniqueActivity
|
|
28
|
+
async lockUser(
|
|
29
|
+
@Inject
|
|
30
|
+
db?: SocialMailContext
|
|
31
|
+
) {
|
|
32
|
+
const max = 5;
|
|
33
|
+
|
|
34
|
+
const all = await db.loginSessions.where(this.input, (p) => (x) => x.userID === p.userID
|
|
35
|
+
&& x.status !== "created"
|
|
36
|
+
&& x.dateUpdated > Sql.date.addMinutes(Sql.date.now(), -5)
|
|
37
|
+
)
|
|
38
|
+
.orderByDescending(void 0, (p) => (x) => x.sessionID)
|
|
39
|
+
.limit(max)
|
|
40
|
+
.count();
|
|
41
|
+
|
|
42
|
+
if (max !== all) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const loginLockedTill = DateTime.now.addHours(1);
|
|
47
|
+
const { userID } = this.input;
|
|
48
|
+
|
|
49
|
+
await db.users.statements.update({ loginLockedTill }, { userID });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
}
|