@restingowlorg/owlauth 1.0.1

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.
Files changed (69) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +307 -0
  3. package/dist/config.d.ts +13 -0
  4. package/dist/config.js +16 -0
  5. package/dist/core/auth.manager.d.ts +6 -0
  6. package/dist/core/auth.manager.js +21 -0
  7. package/dist/core/auth.service.init.d.ts +2 -0
  8. package/dist/core/auth.service.init.js +26 -0
  9. package/dist/index.d.ts +5 -0
  10. package/dist/index.js +11 -0
  11. package/dist/infra/databases/mongo/adapter.d.ts +6 -0
  12. package/dist/infra/databases/mongo/adapter.js +24 -0
  13. package/dist/infra/databases/mongo/db.d.ts +5 -0
  14. package/dist/infra/databases/mongo/db.js +43 -0
  15. package/dist/infra/databases/mongo/mongo.d.ts +5 -0
  16. package/dist/infra/databases/mongo/mongo.js +11 -0
  17. package/dist/infra/databases/postgresql/adapter.d.ts +6 -0
  18. package/dist/infra/databases/postgresql/adapter.js +26 -0
  19. package/dist/infra/databases/postgresql/db.d.ts +5 -0
  20. package/dist/infra/databases/postgresql/db.js +50 -0
  21. package/dist/infra/databases/postgresql/helpers.d.ts +8 -0
  22. package/dist/infra/databases/postgresql/helpers.js +55 -0
  23. package/dist/infra/databases/postgresql/postgres.d.ts +5 -0
  24. package/dist/infra/databases/postgresql/postgres.js +11 -0
  25. package/dist/infra/databases/postgresql/schema.d.ts +6 -0
  26. package/dist/infra/databases/postgresql/schema.js +9 -0
  27. package/dist/infra/security/bcrypt.adapter.d.ts +9 -0
  28. package/dist/infra/security/bcrypt.adapter.js +62 -0
  29. package/dist/infra/security/bcrypt.adapter.test.d.ts +1 -0
  30. package/dist/infra/security/bcrypt.adapter.test.js +67 -0
  31. package/dist/infra/security/pwned-passwords.d.ts +5 -0
  32. package/dist/infra/security/pwned-passwords.js +45 -0
  33. package/dist/infra/security/pwned-passwords.test.d.ts +1 -0
  34. package/dist/infra/security/pwned-passwords.test.js +62 -0
  35. package/dist/infra/security/security-audit-logger.d.ts +11 -0
  36. package/dist/infra/security/security-audit-logger.js +90 -0
  37. package/dist/repositories/contracts.d.ts +21 -0
  38. package/dist/repositories/contracts.js +2 -0
  39. package/dist/repositories/mongo/magicLink.repo.d.ts +26 -0
  40. package/dist/repositories/mongo/magicLink.repo.js +106 -0
  41. package/dist/repositories/mongo/user.repo.d.ts +16 -0
  42. package/dist/repositories/mongo/user.repo.js +84 -0
  43. package/dist/repositories/postgresql/magic.link.repo.d.ts +21 -0
  44. package/dist/repositories/postgresql/magic.link.repo.js +97 -0
  45. package/dist/repositories/postgresql/user.repo.d.ts +14 -0
  46. package/dist/repositories/postgresql/user.repo.js +50 -0
  47. package/dist/services/auth.service.d.ts +22 -0
  48. package/dist/services/auth.service.js +362 -0
  49. package/dist/services/auth.service.test.d.ts +1 -0
  50. package/dist/services/auth.service.test.js +297 -0
  51. package/dist/services/magic-link.service.d.ts +22 -0
  52. package/dist/services/magic-link.service.js +196 -0
  53. package/dist/services/magic-link.service.test.d.ts +1 -0
  54. package/dist/services/magic-link.service.test.js +230 -0
  55. package/dist/strategies/CredentialsStrategy.d.ts +4 -0
  56. package/dist/strategies/CredentialsStrategy.js +32 -0
  57. package/dist/strategies/CredentialsStrategy.test.d.ts +1 -0
  58. package/dist/strategies/CredentialsStrategy.test.js +29 -0
  59. package/dist/strategies/MagicLinkStrategy.d.ts +4 -0
  60. package/dist/strategies/MagicLinkStrategy.js +21 -0
  61. package/dist/strategies/MagicLinkStrategy.test.d.ts +1 -0
  62. package/dist/strategies/MagicLinkStrategy.test.js +38 -0
  63. package/dist/types/index.d.ts +224 -0
  64. package/dist/types/index.js +2 -0
  65. package/dist/utils/check-blocked-passwords.d.ts +1 -0
  66. package/dist/utils/check-blocked-passwords.js +10 -0
  67. package/dist/utils/check-blocked-passwords.test.d.ts +1 -0
  68. package/dist/utils/check-blocked-passwords.test.js +27 -0
  69. package/package.json +102 -0
@@ -0,0 +1,230 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const magic_link_service_1 = require("./magic-link.service");
4
+ describe("MagicLinkService", () => {
5
+ let service;
6
+ let mockUserRepo;
7
+ let mockMagicLinkRepo;
8
+ let mockCrypto;
9
+ let mockLogger;
10
+ beforeEach(() => {
11
+ mockUserRepo = {
12
+ findById: jest.fn(),
13
+ findByEmail: jest.fn(),
14
+ findByUsername: jest.fn(),
15
+ create: jest.fn(),
16
+ updatePassword: jest.fn()
17
+ };
18
+ mockMagicLinkRepo = {
19
+ create: jest.fn(),
20
+ findAll: jest.fn(),
21
+ findByTokenHash: jest.fn(),
22
+ findById: jest.fn(),
23
+ consume: jest.fn(),
24
+ invalidateByUserId: jest.fn(),
25
+ deleteByUserId: jest.fn()
26
+ };
27
+ mockCrypto = {
28
+ hashPassword: jest.fn(),
29
+ verifyPassword: jest.fn(),
30
+ generateToken: jest.fn(),
31
+ hashToken: jest.fn(),
32
+ verifyToken: jest.fn()
33
+ };
34
+ mockLogger = {
35
+ audit: jest.fn(),
36
+ info: jest.fn(),
37
+ warn: jest.fn(),
38
+ error: jest.fn()
39
+ };
40
+ service = new magic_link_service_1.MagicLinkService(mockUserRepo, mockMagicLinkRepo, mockCrypto, mockLogger);
41
+ });
42
+ describe("request", () => {
43
+ const email = "test@example.com";
44
+ it("should successfully request a magic link", async () => {
45
+ mockUserRepo.findByEmail.mockResolvedValue({ id: "1", email });
46
+ mockMagicLinkRepo.invalidateByUserId.mockResolvedValue(true);
47
+ mockCrypto.generateToken.mockReturnValue("raw_token");
48
+ mockCrypto.hashToken.mockResolvedValue("hashed_token");
49
+ mockMagicLinkRepo.create.mockResolvedValue({ id: "link1" });
50
+ const result = await service.request(email);
51
+ expect(result.success).toBe(true);
52
+ if (result.success && result.data) {
53
+ expect(result.data).toBe("link1.raw_token");
54
+ }
55
+ // eslint-disable-next-line @typescript-eslint/unbound-method
56
+ expect(mockLogger.audit).toHaveBeenCalledWith(expect.objectContaining({ type: "MAGIC_LINK_REQUESTED" }));
57
+ });
58
+ it("should fail if user not found", async () => {
59
+ mockUserRepo.findByEmail.mockResolvedValue(null);
60
+ const result = await service.request(email);
61
+ expect(result.success).toBe(false);
62
+ expect(result.httpCode).toBe(404);
63
+ });
64
+ it("should fail if invalidation fails", async () => {
65
+ mockUserRepo.findByEmail.mockResolvedValue({ id: "1", email });
66
+ mockMagicLinkRepo.invalidateByUserId.mockResolvedValue(false);
67
+ const result = await service.request(email);
68
+ expect(result.success).toBe(false);
69
+ expect(result.httpCode).toBe(500);
70
+ });
71
+ it("should fail if creation fails", async () => {
72
+ mockUserRepo.findByEmail.mockResolvedValue({ id: "1", email });
73
+ mockMagicLinkRepo.invalidateByUserId.mockResolvedValue(true);
74
+ mockCrypto.generateToken.mockReturnValue("t");
75
+ mockCrypto.hashToken.mockResolvedValue("ht");
76
+ mockMagicLinkRepo.create.mockResolvedValue(null);
77
+ const result = await service.request(email);
78
+ expect(result.success).toBe(false);
79
+ expect(result.httpCode).toBe(500);
80
+ });
81
+ it("should propagate correlationId to auditLogger and error logs during request", async () => {
82
+ const correlationId = "magic-req-id";
83
+ mockUserRepo.findByEmail.mockResolvedValue({ id: "1", email });
84
+ mockMagicLinkRepo.invalidateByUserId.mockResolvedValue(true);
85
+ mockCrypto.generateToken.mockReturnValue("t");
86
+ mockCrypto.hashToken.mockResolvedValue("ht");
87
+ mockMagicLinkRepo.create.mockResolvedValue({ id: "l" });
88
+ // 1. Audit log on success
89
+ await service.request(email, { correlationId });
90
+ // eslint-disable-next-line @typescript-eslint/unbound-method
91
+ expect(mockLogger.audit).toHaveBeenCalledWith(expect.objectContaining({ type: "MAGIC_LINK_REQUESTED", correlationId }));
92
+ // 2. Error log on exception
93
+ const error = new Error("DB Error");
94
+ mockUserRepo.findByEmail.mockRejectedValue(error);
95
+ await service.request(email, { correlationId });
96
+ // eslint-disable-next-line @typescript-eslint/unbound-method
97
+ expect(mockLogger.error).toHaveBeenCalledWith("Magic link request exception", error, { email }, correlationId);
98
+ });
99
+ });
100
+ describe("verify", () => {
101
+ it("should successfully verify a token", async () => {
102
+ const token = "link1.raw_token";
103
+ mockMagicLinkRepo.findById.mockResolvedValue({
104
+ id: "link1",
105
+ userId: "1",
106
+ tokenHash: "hashed_token",
107
+ expiresAt: new Date(Date.now() + 3600000),
108
+ usedAt: null
109
+ });
110
+ mockCrypto.verifyToken.mockResolvedValue(true);
111
+ const result = await service.verify(token);
112
+ expect(result.success).toBe(true);
113
+ if (result.success && result.data) {
114
+ expect(result.data.userId).toBe("1");
115
+ }
116
+ // eslint-disable-next-line @typescript-eslint/unbound-method
117
+ expect(mockLogger.audit).toHaveBeenCalledWith(expect.objectContaining({ type: "MAGIC_LINK_VERIFIED" }));
118
+ });
119
+ it("should fail if token is malformed", async () => {
120
+ const result = await service.verify("invalid_token");
121
+ expect(result.success).toBe(false);
122
+ expect(result.httpCode).toBe(400);
123
+ });
124
+ it("should fail if record not found", async () => {
125
+ mockMagicLinkRepo.findById.mockResolvedValue(null);
126
+ const result = await service.verify("link1.t");
127
+ expect(result.success).toBe(false);
128
+ expect(result.httpCode).toBe(401);
129
+ });
130
+ it("should fail if token expired", async () => {
131
+ mockMagicLinkRepo.findById.mockResolvedValue({
132
+ expiresAt: new Date(Date.now() - 1000),
133
+ usedAt: null
134
+ });
135
+ const result = await service.verify("link1.t");
136
+ expect(result.success).toBe(false);
137
+ expect(result.message).toContain("expired");
138
+ });
139
+ it("should fail if token already used", async () => {
140
+ mockMagicLinkRepo.findById.mockResolvedValue({
141
+ expiresAt: new Date(Date.now() + 1000),
142
+ usedAt: new Date()
143
+ });
144
+ const result = await service.verify("link1.t");
145
+ expect(result.success).toBe(false);
146
+ expect(result.message).toContain("expired"); // Service message is same for both
147
+ });
148
+ it("should propagate correlationId to auditLogger and error logs during verification", async () => {
149
+ const token = "link1.raw_token";
150
+ const correlationId = "magic-verify-id";
151
+ // 1. Audit log on failure
152
+ mockMagicLinkRepo.findById.mockResolvedValue(null);
153
+ await service.verify(token, { correlationId });
154
+ // eslint-disable-next-line @typescript-eslint/unbound-method
155
+ expect(mockLogger.audit).toHaveBeenCalledWith(expect.objectContaining({ type: "MAGIC_LINK_FAILURE", correlationId }));
156
+ // 2. Error log on exception
157
+ const error = new Error("DB Error");
158
+ mockMagicLinkRepo.findById.mockRejectedValue(error);
159
+ await service.verify(token, { correlationId });
160
+ // eslint-disable-next-line @typescript-eslint/unbound-method
161
+ expect(mockLogger.error).toHaveBeenCalledWith("Magic link verify exception", error, undefined, correlationId);
162
+ });
163
+ });
164
+ describe("consume", () => {
165
+ const token = "link1.raw_token";
166
+ it("should successfully consume a token", async () => {
167
+ mockMagicLinkRepo.findById.mockResolvedValue({
168
+ id: "link1",
169
+ userId: "123",
170
+ tokenHash: "ht",
171
+ expiresAt: new Date(Date.now() + 1000),
172
+ usedAt: null
173
+ });
174
+ mockCrypto.verifyToken.mockResolvedValue(true);
175
+ mockMagicLinkRepo.consume.mockResolvedValue(true);
176
+ const result = await service.consume(token);
177
+ expect(result.success).toBe(true);
178
+ if (result.success && result.data) {
179
+ expect(result.data.userId).toBe("123");
180
+ }
181
+ // eslint-disable-next-line @typescript-eslint/unbound-method
182
+ expect(mockMagicLinkRepo.consume).toHaveBeenCalledWith("link1");
183
+ // eslint-disable-next-line @typescript-eslint/unbound-method
184
+ expect(mockLogger.audit).toHaveBeenCalledWith(expect.objectContaining({ type: "MAGIC_LINK_CONSUMED" }));
185
+ // eslint-disable-next-line @typescript-eslint/unbound-method
186
+ expect(mockLogger.audit).toHaveBeenCalledWith(expect.objectContaining({ type: "LOGIN_SUCCESS", metadata: { method: "magic-link" } }));
187
+ });
188
+ it("should fail if verification fails", async () => {
189
+ mockMagicLinkRepo.findById.mockResolvedValue(null);
190
+ const result = await service.consume(token);
191
+ expect(result.success).toBe(false);
192
+ });
193
+ it("should fail if consume fails", async () => {
194
+ mockMagicLinkRepo.findById.mockResolvedValue({
195
+ id: "link1",
196
+ userId: "123",
197
+ expiresAt: new Date(Date.now() + 1000),
198
+ usedAt: null
199
+ });
200
+ mockCrypto.verifyToken.mockResolvedValue(true);
201
+ mockMagicLinkRepo.consume.mockResolvedValue(false);
202
+ const result = await service.consume(token);
203
+ expect(result.success).toBe(false);
204
+ expect(result.httpCode).toBe(401);
205
+ });
206
+ it("should propagate correlationId to auditLogger and error logs during consumption", async () => {
207
+ const token = "link1.raw_token";
208
+ const correlationId = "magic-consume-id";
209
+ // 1. Audit log on failure
210
+ mockMagicLinkRepo.findById.mockResolvedValue(null);
211
+ await service.consume(token, { correlationId });
212
+ // eslint-disable-next-line @typescript-eslint/unbound-method
213
+ expect(mockLogger.audit).toHaveBeenCalledWith(expect.objectContaining({ type: "MAGIC_LINK_FAILURE", correlationId }));
214
+ // 2. Error log on exception (test consume's own catch block)
215
+ mockMagicLinkRepo.findById.mockResolvedValue({
216
+ id: "link1",
217
+ userId: "123",
218
+ tokenHash: "ht",
219
+ expiresAt: new Date(Date.now() + 1000),
220
+ usedAt: null
221
+ });
222
+ mockCrypto.verifyToken.mockResolvedValue(true);
223
+ const error = new Error("DB Error on Consume");
224
+ mockMagicLinkRepo.consume.mockRejectedValue(error);
225
+ await service.consume(token, { correlationId });
226
+ // eslint-disable-next-line @typescript-eslint/unbound-method
227
+ expect(mockLogger.error).toHaveBeenCalledWith("Magic link consume exception", error, undefined, correlationId);
228
+ });
229
+ });
230
+ });
@@ -0,0 +1,4 @@
1
+ import { AuthDB, AuthOptions, AuthType, IAuthMethods, IAuthStrategy, Mutable } from "../types";
2
+ export declare class CredentialsAuthStrategy implements IAuthStrategy {
3
+ register(target: Mutable<Partial<IAuthMethods>>, db: AuthDB, options: AuthOptions<AuthType>): void;
4
+ }
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CredentialsAuthStrategy = void 0;
4
+ const security_audit_logger_1 = require("../infra/security/security-audit-logger");
5
+ const bcrypt_adapter_1 = require("../infra/security/bcrypt.adapter");
6
+ const auth_service_1 = require("../services/auth.service");
7
+ class CredentialsAuthStrategy {
8
+ register(target, db, options) {
9
+ const cryptoAdapter = options.cryptoAdapter || new bcrypt_adapter_1.BcryptAdapter();
10
+ const service = new auth_service_1.AuthService(db.userRepo, cryptoAdapter, security_audit_logger_1.auditLogger);
11
+ target.credentials = {
12
+ signup: (email, username, password, optionsOverride) => {
13
+ var _a, _b;
14
+ return service.signup(email, username, password, {
15
+ blockedPasswords: (_a = optionsOverride === null || optionsOverride === void 0 ? void 0 : optionsOverride.blockedPasswords) !== null && _a !== void 0 ? _a : options.blockedPasswords,
16
+ pwnedPasswordFailClosed: (_b = optionsOverride === null || optionsOverride === void 0 ? void 0 : optionsOverride.pwnedPasswordFailClosed) !== null && _b !== void 0 ? _b : options.pwnedPasswordFailClosed,
17
+ correlationId: optionsOverride === null || optionsOverride === void 0 ? void 0 : optionsOverride.correlationId
18
+ });
19
+ },
20
+ login: (email, password, optionsOverride) => service.login(email, password, optionsOverride),
21
+ changePassword: (userId, currentPass, newPass, optionsOverride) => {
22
+ var _a, _b;
23
+ return service.changePassword(userId, currentPass, newPass, {
24
+ blockedPasswords: (_a = optionsOverride === null || optionsOverride === void 0 ? void 0 : optionsOverride.blockedPasswords) !== null && _a !== void 0 ? _a : options.blockedPasswords,
25
+ pwnedPasswordFailClosed: (_b = optionsOverride === null || optionsOverride === void 0 ? void 0 : optionsOverride.pwnedPasswordFailClosed) !== null && _b !== void 0 ? _b : options.pwnedPasswordFailClosed,
26
+ correlationId: optionsOverride === null || optionsOverride === void 0 ? void 0 : optionsOverride.correlationId
27
+ });
28
+ }
29
+ };
30
+ }
31
+ }
32
+ exports.CredentialsAuthStrategy = CredentialsAuthStrategy;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const CredentialsStrategy_1 = require("./CredentialsStrategy");
4
+ describe("CredentialsAuthStrategy", () => {
5
+ let strategy;
6
+ let mockDb;
7
+ let mockOptions;
8
+ beforeEach(() => {
9
+ strategy = new CredentialsStrategy_1.CredentialsAuthStrategy();
10
+ mockDb = {
11
+ userRepo: {},
12
+ close: jest.fn()
13
+ };
14
+ mockOptions = {
15
+ adapter: {}
16
+ };
17
+ });
18
+ it("should register signup, login, and changePassword methods under target.credentials", () => {
19
+ const target = {};
20
+ strategy.register(target, mockDb, mockOptions);
21
+ expect(target.credentials).toBeDefined();
22
+ if (target.credentials) {
23
+ expect(target.credentials.signup).toBeDefined();
24
+ expect(target.credentials.login).toBeDefined();
25
+ expect(target.credentials.changePassword).toBeDefined();
26
+ expect(typeof target.credentials.signup).toBe("function");
27
+ }
28
+ });
29
+ });
@@ -0,0 +1,4 @@
1
+ import { AuthDB, AuthOptions, AuthType, IAuthMethods, IAuthStrategy, Mutable } from "../types";
2
+ export declare class MagicLinkAuthStrategy implements IAuthStrategy {
3
+ register(target: Mutable<Partial<IAuthMethods>>, db: AuthDB, options: AuthOptions<AuthType>): void;
4
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MagicLinkAuthStrategy = void 0;
4
+ const security_audit_logger_1 = require("../infra/security/security-audit-logger");
5
+ const bcrypt_adapter_1 = require("../infra/security/bcrypt.adapter");
6
+ const magic_link_service_1 = require("../services/magic-link.service");
7
+ class MagicLinkAuthStrategy {
8
+ register(target, db, options) {
9
+ const cryptoAdapter = options.cryptoAdapter || new bcrypt_adapter_1.BcryptAdapter();
10
+ if (!db.magicLinkRepo) {
11
+ throw new Error("MagicLinkRepository is required for MagicLinkAuthStrategy");
12
+ }
13
+ const service = new magic_link_service_1.MagicLinkService(db.userRepo, db.magicLinkRepo, cryptoAdapter, security_audit_logger_1.auditLogger);
14
+ target.magicLink = {
15
+ request: (email, optionsOverride) => service.request(email, optionsOverride),
16
+ verify: (token, optionsOverride) => service.verify(token, optionsOverride),
17
+ consume: (token, optionsOverride) => service.consume(token, optionsOverride)
18
+ };
19
+ }
20
+ }
21
+ exports.MagicLinkAuthStrategy = MagicLinkAuthStrategy;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const MagicLinkStrategy_1 = require("./MagicLinkStrategy");
4
+ describe("MagicLinkAuthStrategy", () => {
5
+ let strategy;
6
+ let mockDb;
7
+ let mockOptions;
8
+ beforeEach(() => {
9
+ strategy = new MagicLinkStrategy_1.MagicLinkAuthStrategy();
10
+ mockDb = {
11
+ userRepo: {},
12
+ magicLinkRepo: {},
13
+ close: jest.fn()
14
+ };
15
+ mockOptions = {
16
+ adapter: {}
17
+ };
18
+ });
19
+ it("should register request, verify, and consume methods under target.magicLink", () => {
20
+ const target = {};
21
+ strategy.register(target, mockDb, mockOptions);
22
+ expect(target.magicLink).toBeDefined();
23
+ if (target.magicLink) {
24
+ expect(target.magicLink.request).toBeDefined();
25
+ expect(target.magicLink.verify).toBeDefined();
26
+ expect(target.magicLink.consume).toBeDefined();
27
+ expect(typeof target.magicLink.request).toBe("function");
28
+ }
29
+ });
30
+ it("should throw if magicLinkRepo is missing", () => {
31
+ const target = {};
32
+ const dbWithoutRepo = {
33
+ userRepo: {},
34
+ close: jest.fn()
35
+ };
36
+ expect(() => strategy.register(target, dbWithoutRepo, mockOptions)).toThrow("MagicLinkRepository is required for MagicLinkAuthStrategy");
37
+ });
38
+ });
@@ -0,0 +1,224 @@
1
+ import { UserRepository, MagicLinkRepository } from "../repositories/contracts";
2
+ import { PostgresUserSchema } from "../infra/databases/postgresql/schema";
3
+ import { ObjectId } from "mongodb";
4
+ export type AuthType = "credentials" | "magicLink";
5
+ export interface InitPostgresOptions {
6
+ postgresUrl: string;
7
+ userTableName: string;
8
+ userSchema?: string;
9
+ magicLinkTableName?: string;
10
+ magicLinkSchema?: string;
11
+ }
12
+ export type InitMongoOptions = {
13
+ mongoUri: string;
14
+ magicLinkCollectionName?: string;
15
+ userCollectionName: string;
16
+ };
17
+ export interface IDatabaseAdapter {
18
+ connect(options: BaseAuthOptions): Promise<AuthDB>;
19
+ }
20
+ export interface ICryptoAdapter {
21
+ hashPassword(password: string): Promise<string>;
22
+ verifyPassword(password: string, hash: string): Promise<boolean>;
23
+ generateToken(length?: number): string;
24
+ hashToken(token: string): Promise<string>;
25
+ verifyToken(token: string, hash: string): Promise<boolean>;
26
+ }
27
+ export type BaseAuthOptions<T extends AuthType = AuthType> = {
28
+ authTypes?: T[];
29
+ blockedPasswords?: string[];
30
+ magicLinkBaseUrl?: string;
31
+ cryptoAdapter?: ICryptoAdapter;
32
+ customMaskingKeys?: string[];
33
+ pwnedPasswordFailClosed?: boolean;
34
+ };
35
+ export type Mutable<T> = {
36
+ -readonly [P in keyof T]: T[P];
37
+ };
38
+ export interface IAuthStrategy {
39
+ register(target: Mutable<Partial<IAuthMethods>>, db: AuthDB, options: AuthOptions<AuthType>): void;
40
+ }
41
+ export type AuthResult<T = unknown> = {
42
+ success: true;
43
+ data: T;
44
+ httpCode: number;
45
+ message: string;
46
+ } | {
47
+ success: false;
48
+ data?: undefined;
49
+ httpCode: number;
50
+ message: string;
51
+ };
52
+ export type AuthOptions<T extends AuthType = AuthType> = BaseAuthOptions<T> & {
53
+ adapter: IDatabaseAdapter;
54
+ };
55
+ export type AuthLogLevel = "info" | "warn" | "error";
56
+ export type User = {
57
+ id: string;
58
+ email: string;
59
+ username: string;
60
+ password: string;
61
+ };
62
+ export type AuthUser = {
63
+ id: string;
64
+ email: string;
65
+ username?: string;
66
+ };
67
+ export type AuthRequest = Request & {
68
+ user?: AuthUser;
69
+ };
70
+ export type TableColumn = {
71
+ column_name: string;
72
+ is_nullable?: "YES" | "NO";
73
+ data_type?: string;
74
+ };
75
+ export type ColumnRow = {
76
+ column_name: string;
77
+ };
78
+ export type AuthDB = {
79
+ userRepo: UserRepository;
80
+ magicLinkRepo?: MagicLinkRepository;
81
+ close: () => Promise<void>;
82
+ };
83
+ export type SignupInput = {
84
+ email: string;
85
+ username: string;
86
+ password: string;
87
+ };
88
+ export type LoginInput = {
89
+ email: string;
90
+ password: string;
91
+ };
92
+ export type MagicLinkRow = {
93
+ id: number | string;
94
+ user_id: number | string;
95
+ token: string;
96
+ expires_at: Date;
97
+ used_at: Date | null;
98
+ created_at: Date;
99
+ };
100
+ export type MagicLinkToken = {
101
+ id: number | string;
102
+ userId: string | number;
103
+ tokenHash: string;
104
+ expiresAt: Date;
105
+ usedAt: Date | null;
106
+ createdAt: Date;
107
+ };
108
+ export type CreateMagicLinkInput = {
109
+ userId: string;
110
+ tokenHash: string;
111
+ expiresAt: Date;
112
+ usedAt?: Date;
113
+ };
114
+ export interface CreateUserInput {
115
+ email: string;
116
+ passwordHash: string;
117
+ username: string;
118
+ }
119
+ export interface ICredentialsMethods {
120
+ readonly signup: (email: string, username: string, password: string, options?: {
121
+ blockedPasswords?: string[];
122
+ pwnedPasswordFailClosed?: boolean;
123
+ correlationId?: string;
124
+ }) => Promise<AuthResult<SignupResult>>;
125
+ readonly login: (email: string, password: string, options?: {
126
+ correlationId?: string;
127
+ }) => Promise<AuthResult<LoginResult>>;
128
+ readonly changePassword: (userId: string | number, currentPassword: string, newPassword: string, options?: {
129
+ blockedPasswords?: string[];
130
+ pwnedPasswordFailClosed?: boolean;
131
+ correlationId?: string;
132
+ }) => Promise<AuthResult<ChangePasswordResult>>;
133
+ }
134
+ export interface IMagicLinkMethods {
135
+ readonly request: (email: string, options?: {
136
+ correlationId?: string;
137
+ }) => Promise<AuthResult<RequestMagicLinkResult>>;
138
+ readonly verify: (token: string, options?: {
139
+ correlationId?: string;
140
+ }) => Promise<AuthResult<VerifyMagicLinkResult>>;
141
+ readonly consume: (token: string, options?: {
142
+ correlationId?: string;
143
+ }) => Promise<AuthResult<ConsumeMagicLinkResult>>;
144
+ }
145
+ export interface IAuthMethods {
146
+ credentials: ICredentialsMethods;
147
+ magicLink: IMagicLinkMethods;
148
+ }
149
+ export type IAuthManager<T extends AuthType = AuthType> = {
150
+ [K in T]: IAuthMethods[K];
151
+ } & {
152
+ readonly disconnectDB: () => Promise<void>;
153
+ };
154
+ export interface IMongoMagicLinkDoc {
155
+ _id?: ObjectId;
156
+ user_id: ObjectId;
157
+ token: string;
158
+ expires_at: Date;
159
+ used_at: Date | null;
160
+ created_at: Date;
161
+ updated_at: Date;
162
+ }
163
+ export interface IMongoUserDoc {
164
+ _id?: ObjectId;
165
+ email: string;
166
+ username: string;
167
+ password: string;
168
+ created_at?: Date;
169
+ updated_at?: Date;
170
+ }
171
+ export interface FKRow {
172
+ referenced_table: string;
173
+ referenced_column: string;
174
+ }
175
+ export interface TableExistsRow {
176
+ exists: boolean;
177
+ }
178
+ export interface ColumnInfoRow {
179
+ column_name: string;
180
+ is_nullable: "YES" | "NO";
181
+ }
182
+ export interface PrimaryKeyRow {
183
+ column_name: string;
184
+ data_type: string;
185
+ }
186
+ export type UserId = string | number;
187
+ export type LoginResult = {
188
+ user: SafeUser;
189
+ };
190
+ export type SignupResult = {
191
+ user: SafeUser;
192
+ };
193
+ export type ChangePasswordResult = {
194
+ user: SafeUser;
195
+ };
196
+ export type RequestMagicLinkResult = string;
197
+ export type VerifyMagicLinkResult = {
198
+ isValid: boolean;
199
+ userId: string;
200
+ tokenId: string;
201
+ };
202
+ export type ConsumeMagicLinkResult = {
203
+ userId: string;
204
+ };
205
+ export type SafeUser = {
206
+ id: string | number;
207
+ email: string;
208
+ username: string;
209
+ };
210
+ export type UserColumn = (typeof PostgresUserSchema.requiredColumns)[number];
211
+ export type SecurityEventType = "LOGIN_SUCCESS" | "LOGIN_FAILURE" | "SIGNUP" | "SIGNUP_FAILURE" | "PASSWORD_CHANGE" | "MAGIC_LINK_REQUESTED" | "MAGIC_LINK_VERIFIED" | "MAGIC_LINK_CONSUMED" | "MAGIC_LINK_FAILURE";
212
+ export type SecurityEvent = {
213
+ type: SecurityEventType;
214
+ userId?: string | number;
215
+ email?: string;
216
+ metadata?: Record<string, unknown>;
217
+ correlationId?: string;
218
+ };
219
+ export interface IAuditLogger {
220
+ info(message: string, context?: unknown, correlationId?: string): void;
221
+ warn(message: string, context?: unknown, correlationId?: string): void;
222
+ error(message: string, error: unknown, context?: unknown, correlationId?: string): void;
223
+ audit(event: SecurityEvent): void;
224
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
1
+ export declare function containsBlockedPasswords(password: string, email: string, username: string, blockedList?: string[]): boolean;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.containsBlockedPasswords = containsBlockedPasswords;
4
+ function containsBlockedPasswords(password, email, username, blockedList = []) {
5
+ const pwd = password.toLowerCase();
6
+ const emailLocal = email.split("@")[0].toLowerCase();
7
+ const uname = username.toLowerCase();
8
+ const blockedTerms = [emailLocal, uname, ...blockedList.map((w) => w.toLowerCase())];
9
+ return blockedTerms.some((term) => term && pwd.includes(term));
10
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const check_blocked_passwords_1 = require("./check-blocked-passwords");
4
+ describe("containsBlockedPasswords", () => {
5
+ const email = "testuser@example.com";
6
+ const username = "testuser";
7
+ const blockedList = ["password", "123456", "admin"];
8
+ it("should return true if password contains the username", () => {
9
+ expect((0, check_blocked_passwords_1.containsBlockedPasswords)("mytestuser123", email, username)).toBe(true);
10
+ });
11
+ it("should return true if password contains the email local part", () => {
12
+ expect((0, check_blocked_passwords_1.containsBlockedPasswords)("testuser_secure", email, username)).toBe(true);
13
+ });
14
+ it("should return true if password contains a term from the blocked list", () => {
15
+ expect((0, check_blocked_passwords_1.containsBlockedPasswords)("i_love_password", email, username, blockedList)).toBe(true);
16
+ });
17
+ it("should return false if password is secure and independent", () => {
18
+ expect((0, check_blocked_passwords_1.containsBlockedPasswords)("Secure-Pwd-2024!", email, username, blockedList)).toBe(false);
19
+ });
20
+ it("should be case-insensitive", () => {
21
+ expect((0, check_blocked_passwords_1.containsBlockedPasswords)("TESTUSER", email, username)).toBe(true);
22
+ expect((0, check_blocked_passwords_1.containsBlockedPasswords)("ADMIN_LOGIN", email, username, blockedList)).toBe(true);
23
+ });
24
+ it("should handle empty blocked list", () => {
25
+ expect((0, check_blocked_passwords_1.containsBlockedPasswords)("admin", email, username, [])).toBe(false);
26
+ });
27
+ });