@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.
- package/LICENSE +373 -0
- package/README.md +307 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.js +16 -0
- package/dist/core/auth.manager.d.ts +6 -0
- package/dist/core/auth.manager.js +21 -0
- package/dist/core/auth.service.init.d.ts +2 -0
- package/dist/core/auth.service.init.js +26 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +11 -0
- package/dist/infra/databases/mongo/adapter.d.ts +6 -0
- package/dist/infra/databases/mongo/adapter.js +24 -0
- package/dist/infra/databases/mongo/db.d.ts +5 -0
- package/dist/infra/databases/mongo/db.js +43 -0
- package/dist/infra/databases/mongo/mongo.d.ts +5 -0
- package/dist/infra/databases/mongo/mongo.js +11 -0
- package/dist/infra/databases/postgresql/adapter.d.ts +6 -0
- package/dist/infra/databases/postgresql/adapter.js +26 -0
- package/dist/infra/databases/postgresql/db.d.ts +5 -0
- package/dist/infra/databases/postgresql/db.js +50 -0
- package/dist/infra/databases/postgresql/helpers.d.ts +8 -0
- package/dist/infra/databases/postgresql/helpers.js +55 -0
- package/dist/infra/databases/postgresql/postgres.d.ts +5 -0
- package/dist/infra/databases/postgresql/postgres.js +11 -0
- package/dist/infra/databases/postgresql/schema.d.ts +6 -0
- package/dist/infra/databases/postgresql/schema.js +9 -0
- package/dist/infra/security/bcrypt.adapter.d.ts +9 -0
- package/dist/infra/security/bcrypt.adapter.js +62 -0
- package/dist/infra/security/bcrypt.adapter.test.d.ts +1 -0
- package/dist/infra/security/bcrypt.adapter.test.js +67 -0
- package/dist/infra/security/pwned-passwords.d.ts +5 -0
- package/dist/infra/security/pwned-passwords.js +45 -0
- package/dist/infra/security/pwned-passwords.test.d.ts +1 -0
- package/dist/infra/security/pwned-passwords.test.js +62 -0
- package/dist/infra/security/security-audit-logger.d.ts +11 -0
- package/dist/infra/security/security-audit-logger.js +90 -0
- package/dist/repositories/contracts.d.ts +21 -0
- package/dist/repositories/contracts.js +2 -0
- package/dist/repositories/mongo/magicLink.repo.d.ts +26 -0
- package/dist/repositories/mongo/magicLink.repo.js +106 -0
- package/dist/repositories/mongo/user.repo.d.ts +16 -0
- package/dist/repositories/mongo/user.repo.js +84 -0
- package/dist/repositories/postgresql/magic.link.repo.d.ts +21 -0
- package/dist/repositories/postgresql/magic.link.repo.js +97 -0
- package/dist/repositories/postgresql/user.repo.d.ts +14 -0
- package/dist/repositories/postgresql/user.repo.js +50 -0
- package/dist/services/auth.service.d.ts +22 -0
- package/dist/services/auth.service.js +362 -0
- package/dist/services/auth.service.test.d.ts +1 -0
- package/dist/services/auth.service.test.js +297 -0
- package/dist/services/magic-link.service.d.ts +22 -0
- package/dist/services/magic-link.service.js +196 -0
- package/dist/services/magic-link.service.test.d.ts +1 -0
- package/dist/services/magic-link.service.test.js +230 -0
- package/dist/strategies/CredentialsStrategy.d.ts +4 -0
- package/dist/strategies/CredentialsStrategy.js +32 -0
- package/dist/strategies/CredentialsStrategy.test.d.ts +1 -0
- package/dist/strategies/CredentialsStrategy.test.js +29 -0
- package/dist/strategies/MagicLinkStrategy.d.ts +4 -0
- package/dist/strategies/MagicLinkStrategy.js +21 -0
- package/dist/strategies/MagicLinkStrategy.test.d.ts +1 -0
- package/dist/strategies/MagicLinkStrategy.test.js +38 -0
- package/dist/types/index.d.ts +224 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/check-blocked-passwords.d.ts +1 -0
- package/dist/utils/check-blocked-passwords.js +10 -0
- package/dist/utils/check-blocked-passwords.test.d.ts +1 -0
- package/dist/utils/check-blocked-passwords.test.js +27 -0
- 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 {};
|