@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,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MongoUserRepo = void 0;
4
+ const mongodb_1 = require("mongodb");
5
+ /**
6
+ * MongoDB implementation of UserRepository
7
+ */
8
+ class MongoUserRepo {
9
+ constructor(collection) {
10
+ this.collection = collection;
11
+ }
12
+ async create(input) {
13
+ const { email, username, passwordHash } = input;
14
+ const now = new Date();
15
+ const result = await this.collection.insertOne({
16
+ email,
17
+ username,
18
+ password: passwordHash,
19
+ created_at: now,
20
+ updated_at: now
21
+ });
22
+ return {
23
+ id: result.insertedId.toString(),
24
+ email,
25
+ username,
26
+ password: passwordHash
27
+ };
28
+ }
29
+ async findByEmail(email) {
30
+ var _a, _b;
31
+ const user = await this.collection.findOne({ email });
32
+ if (!user)
33
+ return null;
34
+ return {
35
+ id: (_b = (_a = user._id) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : "", // safe fallback just in case
36
+ email: user.email,
37
+ username: user.username,
38
+ password: user.password
39
+ };
40
+ }
41
+ async findById(id) {
42
+ var _a, _b;
43
+ let objectId;
44
+ try {
45
+ objectId = new mongodb_1.ObjectId(id);
46
+ }
47
+ catch {
48
+ return null;
49
+ }
50
+ const user = await this.collection.findOne({ _id: objectId });
51
+ if (!user)
52
+ return null;
53
+ return {
54
+ id: (_b = (_a = user._id) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : "",
55
+ email: user.email,
56
+ username: user.username,
57
+ password: user.password
58
+ };
59
+ }
60
+ async findByUsername(username) {
61
+ var _a, _b;
62
+ const user = await this.collection.findOne({ username });
63
+ if (!user)
64
+ return null;
65
+ return {
66
+ id: (_b = (_a = user._id) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : "",
67
+ email: user.email,
68
+ username: user.username,
69
+ password: user.password
70
+ };
71
+ }
72
+ async updatePassword(userId, passwordHash) {
73
+ let objectId;
74
+ try {
75
+ objectId = new mongodb_1.ObjectId(userId.toString());
76
+ }
77
+ catch {
78
+ return false;
79
+ }
80
+ const result = await this.collection.updateOne({ _id: objectId }, { $set: { password: passwordHash, updated_at: new Date() } });
81
+ return result.modifiedCount > 0;
82
+ }
83
+ }
84
+ exports.MongoUserRepo = MongoUserRepo;
@@ -0,0 +1,21 @@
1
+ import { Pool } from "pg";
2
+ import { MagicLinkToken, MagicLinkRow } from "../../types";
3
+ import { MagicLinkRepository } from "../contracts";
4
+ export declare class PostgresMagicLinkRepository implements MagicLinkRepository {
5
+ private readonly table;
6
+ private readonly pool;
7
+ constructor(table: string, pool: Pool);
8
+ private getTable;
9
+ create(token: {
10
+ userId: string;
11
+ tokenHash: string;
12
+ expiresAt: Date;
13
+ usedAt?: Date;
14
+ }): Promise<MagicLinkToken>;
15
+ findByTokenHash(tokenHash: string): Promise<MagicLinkToken | null>;
16
+ findById(id: string | number): Promise<MagicLinkToken | null>;
17
+ consume(id: string | number): Promise<boolean>;
18
+ deleteByUserId(userId: string | number): Promise<boolean>;
19
+ invalidateByUserId(userId: string | number): Promise<boolean>;
20
+ findAll(): Promise<MagicLinkRow[]>;
21
+ }
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PostgresMagicLinkRepository = void 0;
4
+ class PostgresMagicLinkRepository {
5
+ constructor(table, pool) {
6
+ this.table = table;
7
+ this.pool = pool;
8
+ }
9
+ getTable() {
10
+ if (this.table.includes(".")) {
11
+ const [schema, table] = this.table.split(".");
12
+ return `"${schema}"."${table}"`;
13
+ }
14
+ return `"${this.table}"`;
15
+ }
16
+ async create(token) {
17
+ var _a;
18
+ const result = await this.pool.query(`
19
+ INSERT INTO ${this.getTable()} (user_id, token, expires_at, used_at)
20
+ VALUES ($1, $2, $3, $4)
21
+ RETURNING *
22
+ `, [token.userId, token.tokenHash, token.expiresAt, (_a = token.usedAt) !== null && _a !== void 0 ? _a : null]);
23
+ const row = result.rows[0];
24
+ return {
25
+ id: row.id,
26
+ userId: row.user_id,
27
+ tokenHash: row.token,
28
+ expiresAt: row.expires_at,
29
+ usedAt: row.used_at,
30
+ createdAt: row.created_at
31
+ };
32
+ }
33
+ async findByTokenHash(tokenHash) {
34
+ const result = await this.pool.query(`SELECT * FROM ${this.getTable()} WHERE token = $1`, [tokenHash]);
35
+ const row = result.rows[0];
36
+ if (!row)
37
+ return null;
38
+ return {
39
+ id: row.id,
40
+ userId: row.user_id,
41
+ tokenHash: row.token,
42
+ expiresAt: row.expires_at,
43
+ usedAt: row.used_at,
44
+ createdAt: row.created_at
45
+ };
46
+ }
47
+ async findById(id) {
48
+ const result = await this.pool.query(`SELECT * FROM ${this.getTable()} WHERE id = $1`, [id]);
49
+ const row = result.rows[0];
50
+ if (!row)
51
+ return null;
52
+ return {
53
+ id: row.id,
54
+ userId: row.user_id,
55
+ tokenHash: row.token,
56
+ expiresAt: row.expires_at,
57
+ usedAt: row.used_at,
58
+ createdAt: row.created_at
59
+ };
60
+ }
61
+ async consume(id) {
62
+ var _a;
63
+ const result = await this.pool.query(`UPDATE ${this.getTable()} SET used_at = NOW() WHERE id = $1 AND used_at IS NULL`, [id]);
64
+ return ((_a = result.rowCount) !== null && _a !== void 0 ? _a : 0) > 0;
65
+ }
66
+ async deleteByUserId(userId) {
67
+ var _a;
68
+ const result = await this.pool.query(`DELETE FROM ${this.getTable()} WHERE user_id = $1`, [
69
+ userId
70
+ ]);
71
+ return ((_a = result.rowCount) !== null && _a !== void 0 ? _a : 0) > 0;
72
+ }
73
+ async invalidateByUserId(userId) {
74
+ var _a, _b;
75
+ await this.pool.query(`UPDATE ${this.getTable()} SET used_at = NOW() WHERE user_id = $1 AND used_at IS NULL`, [userId]);
76
+ // Confirm that no active tokens remain for this user
77
+ const checkResult = await this.pool.query(`SELECT count(*) as count FROM ${this.getTable()} WHERE user_id = $1 AND used_at IS NULL`, [userId]);
78
+ const row = checkResult.rows[0];
79
+ const activeCount = parseInt((_b = (_a = row === null || row === void 0 ? void 0 : row.count) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : "0", 10);
80
+ return activeCount === 0;
81
+ }
82
+ async findAll() {
83
+ const result = await this.pool.query(`
84
+ SELECT * FROM ${this.getTable()}
85
+ WHERE used_at IS NULL AND expires_at > NOW()
86
+ `);
87
+ return result.rows.map((row) => ({
88
+ id: row.id,
89
+ user_id: row.user_id,
90
+ token: row.token,
91
+ expires_at: row.expires_at,
92
+ used_at: row.used_at,
93
+ created_at: row.created_at
94
+ }));
95
+ }
96
+ }
97
+ exports.PostgresMagicLinkRepository = PostgresMagicLinkRepository;
@@ -0,0 +1,14 @@
1
+ import { Pool } from "pg";
2
+ import { User, CreateUserInput } from "../../types";
3
+ import { UserRepository } from "../contracts";
4
+ export declare class PostgresUserRepository implements UserRepository {
5
+ private readonly table;
6
+ private readonly pool;
7
+ constructor(table: string, pool: Pool);
8
+ private getTable;
9
+ create(input: CreateUserInput): Promise<User>;
10
+ findByEmail(email: string): Promise<User | null>;
11
+ findById(id: string): Promise<User | null>;
12
+ findByUsername(username: string): Promise<User | null>;
13
+ updatePassword(userId: string | number, passwordHash: string): Promise<boolean>;
14
+ }
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PostgresUserRepository = void 0;
4
+ class PostgresUserRepository {
5
+ constructor(table, pool) {
6
+ this.table = table;
7
+ this.pool = pool;
8
+ }
9
+ getTable() {
10
+ if (this.table.includes(".")) {
11
+ const [schema, table] = this.table.split(".");
12
+ return `"${schema}"."${table}"`;
13
+ }
14
+ return `"${this.table}"`;
15
+ }
16
+ async create(input) {
17
+ const { email, username, passwordHash } = input;
18
+ const result = await this.pool.query(`
19
+ INSERT INTO ${this.getTable()} (email, username, password)
20
+ VALUES ($1, $2, $3)
21
+ RETURNING *
22
+ `, [email, username, passwordHash]);
23
+ return result.rows[0];
24
+ }
25
+ async findByEmail(email) {
26
+ var _a;
27
+ const result = await this.pool.query(`SELECT * FROM ${this.getTable()} WHERE email = $1`, [email]);
28
+ return (_a = result.rows[0]) !== null && _a !== void 0 ? _a : null;
29
+ }
30
+ async findById(id) {
31
+ var _a;
32
+ const result = await this.pool.query(`SELECT * FROM ${this.getTable()} WHERE id = $1`, [id]);
33
+ return (_a = result.rows[0]) !== null && _a !== void 0 ? _a : null;
34
+ }
35
+ async findByUsername(username) {
36
+ var _a;
37
+ const result = await this.pool.query(`SELECT * FROM ${this.getTable()} WHERE username = $1`, [username]);
38
+ return (_a = result.rows[0]) !== null && _a !== void 0 ? _a : null;
39
+ }
40
+ async updatePassword(userId, passwordHash) {
41
+ var _a;
42
+ const result = await this.pool.query(`
43
+ UPDATE ${this.getTable()}
44
+ SET password = $1
45
+ WHERE id = $2
46
+ `, [passwordHash, userId]);
47
+ return ((_a = result.rowCount) !== null && _a !== void 0 ? _a : 0) > 0;
48
+ }
49
+ }
50
+ exports.PostgresUserRepository = PostgresUserRepository;
@@ -0,0 +1,22 @@
1
+ import { ICryptoAdapter, IAuditLogger } from "../types";
2
+ import { UserRepository } from "../repositories/contracts";
3
+ import { AuthResult, LoginResult, SignupResult, ChangePasswordResult } from "../types/index";
4
+ export declare class AuthService {
5
+ private readonly users;
6
+ private readonly crypto;
7
+ private readonly logger;
8
+ constructor(users: UserRepository, crypto: ICryptoAdapter, logger: IAuditLogger);
9
+ signup(email: string, username: string, password: string, options?: {
10
+ blockedPasswords?: string[];
11
+ pwnedPasswordFailClosed?: boolean;
12
+ correlationId?: string;
13
+ }): Promise<AuthResult<LoginResult>>;
14
+ login(email: string, password: string, options?: {
15
+ correlationId?: string;
16
+ }): Promise<AuthResult<SignupResult>>;
17
+ changePassword(userId: string | number, currentPassword: string, newPassword: string, options?: {
18
+ blockedPasswords?: string[];
19
+ pwnedPasswordFailClosed?: boolean;
20
+ correlationId?: string;
21
+ }): Promise<AuthResult<ChangePasswordResult>>;
22
+ }
@@ -0,0 +1,362 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AuthService = void 0;
4
+ const core_1 = require("@zxcvbn-ts/core");
5
+ const pwned_passwords_1 = require("../infra/security/pwned-passwords");
6
+ const check_blocked_passwords_1 = require("../utils/check-blocked-passwords");
7
+ class AuthService {
8
+ constructor(users, crypto, logger) {
9
+ this.users = users;
10
+ this.crypto = crypto;
11
+ this.logger = logger;
12
+ }
13
+ async signup(email, username, password, options) {
14
+ try {
15
+ // Basic validation
16
+ if (!email || !username || !password) {
17
+ this.logger.audit({
18
+ type: "SIGNUP_FAILURE",
19
+ email,
20
+ metadata: { reason: "Missing required fields" },
21
+ correlationId: options === null || options === void 0 ? void 0 : options.correlationId
22
+ });
23
+ return {
24
+ success: false,
25
+ data: undefined,
26
+ message: "Email, username, and password are required.",
27
+ httpCode: 400
28
+ };
29
+ }
30
+ if (!/^[a-zA-Z0-9_]{3,20}$/.test(username)) {
31
+ this.logger.audit({
32
+ type: "SIGNUP_FAILURE",
33
+ email,
34
+ metadata: { username, reason: "Invalid username format" },
35
+ correlationId: options === null || options === void 0 ? void 0 : options.correlationId
36
+ });
37
+ return {
38
+ success: false,
39
+ data: undefined,
40
+ message: "Invalid username format.",
41
+ httpCode: 400
42
+ };
43
+ }
44
+ if ((0, check_blocked_passwords_1.containsBlockedPasswords)(password, email, username, options === null || options === void 0 ? void 0 : options.blockedPasswords)) {
45
+ this.logger.audit({
46
+ type: "SIGNUP_FAILURE",
47
+ email,
48
+ metadata: { username, reason: "Password contains blocked keywords" },
49
+ correlationId: options === null || options === void 0 ? void 0 : options.correlationId
50
+ });
51
+ return {
52
+ success: false,
53
+ data: undefined,
54
+ message: "Password cannot contain username, email, or blocked words",
55
+ httpCode: 400
56
+ };
57
+ }
58
+ // Password strength
59
+ if ((0, core_1.zxcvbn)(password).score < 3) {
60
+ this.logger.audit({
61
+ type: "SIGNUP_FAILURE",
62
+ email,
63
+ metadata: { username, reason: "Password too weak" },
64
+ correlationId: options === null || options === void 0 ? void 0 : options.correlationId
65
+ });
66
+ return { success: false, data: undefined, message: "Password too weak.", httpCode: 400 };
67
+ }
68
+ // Breached password check
69
+ const breachCheck = await (0, pwned_passwords_1.isBreachedPassword)(password);
70
+ if (breachCheck.detected) {
71
+ this.logger.audit({
72
+ type: "SIGNUP_FAILURE",
73
+ email,
74
+ metadata: { username, reason: "Password found in data breach" },
75
+ correlationId: options === null || options === void 0 ? void 0 : options.correlationId
76
+ });
77
+ return {
78
+ success: false,
79
+ data: undefined,
80
+ message: "Password found in data breach.",
81
+ httpCode: 400
82
+ };
83
+ }
84
+ if (breachCheck.error && (options === null || options === void 0 ? void 0 : options.pwnedPasswordFailClosed)) {
85
+ this.logger.audit({
86
+ type: "SIGNUP_FAILURE",
87
+ email,
88
+ metadata: { username, reason: "Breached password check failed (Fail-Closed)" },
89
+ correlationId: options === null || options === void 0 ? void 0 : options.correlationId
90
+ });
91
+ return {
92
+ success: false,
93
+ data: undefined,
94
+ message: "Security check unavailable. Please try again later.",
95
+ httpCode: 503
96
+ };
97
+ }
98
+ // Username uniqueness
99
+ if (this.users.findByUsername) {
100
+ const existingUser = await this.users.findByUsername(username);
101
+ if (existingUser) {
102
+ this.logger.audit({
103
+ type: "SIGNUP_FAILURE",
104
+ email,
105
+ metadata: { username, reason: "Username already taken" },
106
+ correlationId: options === null || options === void 0 ? void 0 : options.correlationId
107
+ });
108
+ return {
109
+ success: false,
110
+ data: undefined,
111
+ message: "Username already taken.",
112
+ httpCode: 400
113
+ };
114
+ }
115
+ }
116
+ // Email uniqueness
117
+ const existingEmail = await this.users.findByEmail(email);
118
+ if (existingEmail) {
119
+ this.logger.audit({
120
+ type: "SIGNUP_FAILURE",
121
+ email,
122
+ metadata: { username, reason: "Email already registered" },
123
+ correlationId: options === null || options === void 0 ? void 0 : options.correlationId
124
+ });
125
+ return {
126
+ success: false,
127
+ data: undefined,
128
+ message: "Email already registered.",
129
+ httpCode: 400
130
+ };
131
+ }
132
+ // Hash password and create user
133
+ const passwordHash = await this.crypto.hashPassword(password);
134
+ const input = { email, username, passwordHash };
135
+ const user = await this.users.create(input);
136
+ if (!user) {
137
+ this.logger.error("Signup failed", new Error("User creation returned null"), { email }, options === null || options === void 0 ? void 0 : options.correlationId);
138
+ return {
139
+ success: false,
140
+ data: undefined,
141
+ message: "Failed to create user",
142
+ httpCode: 500
143
+ };
144
+ }
145
+ this.logger.audit({
146
+ type: "SIGNUP",
147
+ email: user.email,
148
+ correlationId: options === null || options === void 0 ? void 0 : options.correlationId
149
+ });
150
+ return {
151
+ success: true,
152
+ data: {
153
+ user: {
154
+ id: user.id,
155
+ email: user.email,
156
+ username: user.username
157
+ }
158
+ },
159
+ message: "User signed up",
160
+ httpCode: 201
161
+ };
162
+ }
163
+ catch (err) {
164
+ const message = err instanceof Error ? err.message : "Unknown error";
165
+ this.logger.error("Signup exception", err, { email }, options === null || options === void 0 ? void 0 : options.correlationId);
166
+ return {
167
+ success: false,
168
+ data: undefined,
169
+ message: "Signup failed: " + message,
170
+ httpCode: 500
171
+ };
172
+ }
173
+ }
174
+ async login(email, password, options) {
175
+ try {
176
+ // -------------------- Input Validation --------------------
177
+ if (!email || !password) {
178
+ this.logger.audit({
179
+ type: "LOGIN_FAILURE",
180
+ email,
181
+ metadata: { reason: "Missing required fields" },
182
+ correlationId: options === null || options === void 0 ? void 0 : options.correlationId
183
+ });
184
+ return {
185
+ success: false,
186
+ data: undefined,
187
+ message: "Email and password are required.",
188
+ httpCode: 400
189
+ };
190
+ }
191
+ // -------------------- Find User --------------------
192
+ const user = await this.users.findByEmail(email);
193
+ if (!user) {
194
+ this.logger.audit({
195
+ type: "LOGIN_FAILURE",
196
+ email,
197
+ metadata: { reason: "User not found" },
198
+ correlationId: options === null || options === void 0 ? void 0 : options.correlationId
199
+ });
200
+ return {
201
+ success: false,
202
+ data: undefined,
203
+ message: "Invalid credentials.",
204
+ httpCode: 401
205
+ };
206
+ }
207
+ // -------------------- Verify Password --------------------
208
+ const valid = await this.crypto.verifyPassword(password, user.password);
209
+ if (!valid) {
210
+ this.logger.audit({
211
+ type: "LOGIN_FAILURE",
212
+ email,
213
+ metadata: { reason: "Invalid password" },
214
+ correlationId: options === null || options === void 0 ? void 0 : options.correlationId
215
+ });
216
+ return {
217
+ success: false,
218
+ data: undefined,
219
+ message: "Invalid credentials.",
220
+ httpCode: 401
221
+ };
222
+ }
223
+ // -------------------- Return Safe Response --------------------
224
+ this.logger.audit({ type: "LOGIN_SUCCESS", email, correlationId: options === null || options === void 0 ? void 0 : options.correlationId });
225
+ return {
226
+ success: true,
227
+ data: {
228
+ user: {
229
+ id: user.id,
230
+ email: user.email,
231
+ username: user.username
232
+ }
233
+ },
234
+ message: "User logged in",
235
+ httpCode: 200
236
+ };
237
+ }
238
+ catch (err) {
239
+ this.logger.error("Login exception", err, { email }, options === null || options === void 0 ? void 0 : options.correlationId);
240
+ return {
241
+ success: false,
242
+ data: undefined,
243
+ message: "Login failed : Unknown error",
244
+ httpCode: 500
245
+ };
246
+ }
247
+ }
248
+ async changePassword(userId, currentPassword, newPassword, options) {
249
+ try {
250
+ // Fetch user
251
+ const user = await this.users.findById(userId);
252
+ if (!user) {
253
+ return { success: false, data: undefined, message: "User not found", httpCode: 404 };
254
+ }
255
+ // Verify current password
256
+ const valid = await this.crypto.verifyPassword(currentPassword, user.password);
257
+ if (!valid) {
258
+ this.logger.audit({
259
+ type: "PASSWORD_CHANGE",
260
+ metadata: { success: false, reason: "Current password incorrect" },
261
+ correlationId: options === null || options === void 0 ? void 0 : options.correlationId
262
+ });
263
+ return {
264
+ success: false,
265
+ data: undefined,
266
+ message: "Current password incorrect",
267
+ httpCode: 401
268
+ };
269
+ }
270
+ // Check blocked passwords
271
+ if ((0, check_blocked_passwords_1.containsBlockedPasswords)(newPassword, user.email, user.username, options === null || options === void 0 ? void 0 : options.blockedPasswords)) {
272
+ this.logger.audit({
273
+ type: "PASSWORD_CHANGE",
274
+ metadata: { success: false, reason: "New password contains blocked keywords" },
275
+ correlationId: options === null || options === void 0 ? void 0 : options.correlationId
276
+ });
277
+ return {
278
+ success: false,
279
+ data: undefined,
280
+ message: "New password cannot contain username, email, or blocked words",
281
+ httpCode: 400
282
+ };
283
+ }
284
+ // Password strength
285
+ if ((0, core_1.zxcvbn)(newPassword).score < 3) {
286
+ this.logger.audit({
287
+ type: "PASSWORD_CHANGE",
288
+ metadata: { success: false, reason: "New password too weak" },
289
+ correlationId: options === null || options === void 0 ? void 0 : options.correlationId
290
+ });
291
+ return { success: false, data: undefined, message: "New password too weak", httpCode: 400 };
292
+ }
293
+ // Breach check
294
+ const breachCheck = await (0, pwned_passwords_1.isBreachedPassword)(newPassword);
295
+ if (breachCheck.detected) {
296
+ this.logger.audit({
297
+ type: "PASSWORD_CHANGE",
298
+ metadata: { success: false, reason: "New password found in data breach" },
299
+ correlationId: options === null || options === void 0 ? void 0 : options.correlationId
300
+ });
301
+ return {
302
+ success: false,
303
+ data: undefined,
304
+ message: "New password found in data breach",
305
+ httpCode: 400
306
+ };
307
+ }
308
+ if (breachCheck.error && (options === null || options === void 0 ? void 0 : options.pwnedPasswordFailClosed)) {
309
+ this.logger.audit({
310
+ type: "PASSWORD_CHANGE",
311
+ metadata: { success: false, reason: "Breached password check failed (Fail-Closed)" },
312
+ correlationId: options === null || options === void 0 ? void 0 : options.correlationId
313
+ });
314
+ return {
315
+ success: false,
316
+ data: undefined,
317
+ message: "Security check unavailable. Please try again later.",
318
+ httpCode: 503
319
+ };
320
+ }
321
+ // Hash new password and update
322
+ const newHash = await this.crypto.hashPassword(newPassword);
323
+ const updated = await this.users.updatePassword(userId, newHash);
324
+ if (!updated) {
325
+ this.logger.error("Password update failed", new Error("Database update returned false"), undefined, options === null || options === void 0 ? void 0 : options.correlationId);
326
+ return {
327
+ success: false,
328
+ data: undefined,
329
+ message: "Failed to update password",
330
+ httpCode: 500
331
+ };
332
+ }
333
+ this.logger.audit({
334
+ type: "PASSWORD_CHANGE",
335
+ metadata: { success: true },
336
+ correlationId: options === null || options === void 0 ? void 0 : options.correlationId
337
+ });
338
+ return {
339
+ success: true,
340
+ message: "Password updated successfully",
341
+ data: {
342
+ user: {
343
+ id: user.id,
344
+ email: user.email,
345
+ username: user.username
346
+ }
347
+ },
348
+ httpCode: 200
349
+ };
350
+ }
351
+ catch (err) {
352
+ this.logger.error("Password change exception", err, undefined, options === null || options === void 0 ? void 0 : options.correlationId);
353
+ return {
354
+ success: false,
355
+ data: undefined,
356
+ message: "Password change failed",
357
+ httpCode: 500
358
+ };
359
+ }
360
+ }
361
+ }
362
+ exports.AuthService = AuthService;
@@ -0,0 +1 @@
1
+ export {};