@soapjs/soap-auth 0.3.3 → 0.4.4
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/.claude/settings.local.json +20 -0
- package/build/errors.d.ts +1 -1
- package/build/errors.js +2 -2
- package/build/index.d.ts +1 -0
- package/build/index.js +1 -0
- package/build/services/auth-throttle.service.d.ts +2 -2
- package/build/services/auth-throttle.service.js +2 -2
- package/build/services/index.d.ts +1 -0
- package/build/services/index.js +1 -0
- package/build/services/password.service.d.ts +7 -5
- package/build/services/password.service.js +76 -18
- package/build/services/totp.service.d.ts +16 -0
- package/build/services/totp.service.js +96 -0
- package/build/session/session-handler.d.ts +1 -0
- package/build/session/session-handler.js +39 -6
- package/build/soap-auth.d.ts +13 -6
- package/build/soap-auth.js +132 -5
- package/build/strategies/api-key/api-key.strategy.d.ts +4 -4
- package/build/strategies/api-key/api-key.strategy.js +3 -2
- package/build/strategies/base-auth.strategy.d.ts +5 -4
- package/build/strategies/basic/basic.strategy.d.ts +2 -1
- package/build/strategies/basic/basic.strategy.js +1 -0
- package/build/strategies/credential-auth.strategy.d.ts +7 -7
- package/build/strategies/credential-auth.strategy.js +9 -14
- package/build/strategies/index.d.ts +1 -0
- package/build/strategies/index.js +1 -0
- package/build/strategies/jwt/jwt.strategy.d.ts +3 -1
- package/build/strategies/jwt/jwt.strategy.js +41 -9
- package/build/strategies/jwt/jwt.tools.js +16 -14
- package/build/strategies/local/local.strategy.d.ts +6 -3
- package/build/strategies/local/local.strategy.js +83 -2
- package/build/strategies/oauth2/hybrid.oauth2.strategy.d.ts +3 -3
- package/build/strategies/oauth2/hybrid.oauth2.strategy.js +1 -6
- package/build/strategies/oauth2/oauth2.errors.d.ts +1 -0
- package/build/strategies/oauth2/oauth2.errors.js +4 -0
- package/build/strategies/oauth2/oauth2.strategy.d.ts +6 -4
- package/build/strategies/oauth2/oauth2.strategy.js +114 -46
- package/build/strategies/oauth2/oauth2.tools.js +2 -2
- package/build/strategies/oauth2/oauth2.types.d.ts +2 -2
- package/build/strategies/oauth2/providers/facebook.strategy.d.ts +11 -0
- package/build/strategies/oauth2/providers/facebook.strategy.js +58 -0
- package/build/strategies/oauth2/providers/github.strategy.d.ts +11 -0
- package/build/strategies/oauth2/providers/github.strategy.js +56 -0
- package/build/strategies/oauth2/providers/google.strategy.d.ts +11 -0
- package/build/strategies/oauth2/providers/google.strategy.js +52 -0
- package/build/strategies/oauth2/providers/http-oauth2.strategy.d.ts +16 -0
- package/build/strategies/oauth2/providers/http-oauth2.strategy.js +49 -0
- package/build/strategies/oauth2/providers/index.d.ts +5 -0
- package/build/strategies/oauth2/providers/index.js +21 -0
- package/build/strategies/oauth2/providers/provider.types.d.ts +7 -0
- package/build/strategies/oauth2/providers/provider.types.js +2 -0
- package/build/strategies/token-auth.strategy.d.ts +4 -4
- package/build/strategies/token-auth.strategy.js +2 -3
- package/build/tools/tools.js +1 -2
- package/build/types.d.ts +31 -32
- package/build/utils/validation.d.ts +23 -0
- package/build/utils/validation.js +139 -0
- package/package.json +8 -7
- package/build/__tests__/soap-auth.test.d.ts +0 -1
- package/build/__tests__/soap-auth.test.js +0 -42
- package/build/services/__tests__/account-lock.service.test.d.ts +0 -1
- package/build/services/__tests__/account-lock.service.test.js +0 -55
- package/build/services/__tests__/auth-throttle.service.test.d.ts +0 -1
- package/build/services/__tests__/auth-throttle.service.test.js +0 -48
- package/build/services/__tests__/jwks.service.test.d.ts +0 -1
- package/build/services/__tests__/jwks.service.test.js +0 -39
- package/build/services/__tests__/mfa.service.test.d.ts +0 -1
- package/build/services/__tests__/mfa.service.test.js +0 -66
- package/build/services/__tests__/password.service.test.d.ts +0 -1
- package/build/services/__tests__/password.service.test.js +0 -66
- package/build/services/__tests__/pkce.service.test.d.ts +0 -1
- package/build/services/__tests__/pkce.service.test.js +0 -77
- package/build/services/__tests__/rate-limit.service.test.d.ts +0 -1
- package/build/services/__tests__/rate-limit.service.test.js +0 -37
- package/build/services/__tests__/role.service.test.d.ts +0 -1
- package/build/services/__tests__/role.service.test.js +0 -31
- package/build/session/__tests__/file.session-store.test.d.ts +0 -1
- package/build/session/__tests__/file.session-store.test.js +0 -117
- package/build/session/__tests__/memory.session-store.test.d.ts +0 -1
- package/build/session/__tests__/memory.session-store.test.js +0 -77
- package/build/session/__tests__/session-handler.test.d.ts +0 -1
- package/build/session/__tests__/session-handler.test.js +0 -337
- package/build/strategies/__tests__/base-auth.strategy.test.d.ts +0 -14
- package/build/strategies/__tests__/base-auth.strategy.test.js +0 -137
- package/build/strategies/__tests__/credential-auth.strategy.test.d.ts +0 -14
- package/build/strategies/__tests__/credential-auth.strategy.test.js +0 -265
- package/build/strategies/__tests__/token-auth.strategy.test.d.ts +0 -28
- package/build/strategies/__tests__/token-auth.strategy.test.js +0 -298
- package/build/strategies/api-key/__tests__/api-key.strategy.test.d.ts +0 -1
- package/build/strategies/api-key/__tests__/api-key.strategy.test.js +0 -103
- package/build/strategies/basic/__tests__/basic.strategy.test.d.ts +0 -1
- package/build/strategies/basic/__tests__/basic.strategy.test.js +0 -104
- package/build/strategies/jwt/__tests__/jwt.strategy.test.d.ts +0 -1
- package/build/strategies/jwt/__tests__/jwt.strategy.test.js +0 -156
- package/build/strategies/jwt/__tests__/jwt.tools.test.d.ts +0 -1
- package/build/strategies/jwt/__tests__/jwt.tools.test.js +0 -98
- package/build/strategies/local/__tests__/local.strategy.test.d.ts +0 -1
- package/build/strategies/local/__tests__/local.strategy.test.js +0 -115
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(npm run *)",
|
|
5
|
+
"Bash(npx tsc *)",
|
|
6
|
+
"Bash(npm audit *)",
|
|
7
|
+
"Bash(npm uninstall *)",
|
|
8
|
+
"Bash(npm install *)",
|
|
9
|
+
"Bash(echo \"=== SOAP-EXPRESS src ===\" && find /Users/rad/git/soapjs/soap-express/src -type f -name \"*.ts\" | grep -v __tests__ | grep -v \".test.\" | sort && echo \"\" && echo \"=== SOAP-EXPRESS package.json ===\" && cat /Users/rad/git/soapjs/soap-express/package.json)",
|
|
10
|
+
"Bash(echo \"=== SOAP src \\(http + common\\) ===\" && find /Users/rad/git/soapjs/soap/src -type f -name \"*.ts\" | grep -v __tests__ | grep -v \".test.\" | grep -E \"\\(http|common|config\\)\" | sort)",
|
|
11
|
+
"Bash(cat /Users/rad/git/soapjs/soap/package.json | grep '\"version\"' | head -1)",
|
|
12
|
+
"Bash(grep -rln \"AuthStrategy\\\\|\\\\.configure\\(\\\\|\\\\.middleware\\(\\\\|serializeUser\\\\|AuthConfig\\\\b\" src --include=\"*.test.ts\")",
|
|
13
|
+
"Bash(grep -rln \"AuthStrategy\\\\|AuthConfig\" src/**/__tests__)",
|
|
14
|
+
"Bash(npm pack *)",
|
|
15
|
+
"Read(//Users/rad/git/soapjs/**)",
|
|
16
|
+
"Bash(npm view *)",
|
|
17
|
+
"Bash(grep -v \"^$\")"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
}
|
package/build/errors.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export declare class UserNotFoundError extends Error {
|
|
|
20
20
|
constructor();
|
|
21
21
|
}
|
|
22
22
|
export declare class InvalidCredentialsError extends Error {
|
|
23
|
-
constructor();
|
|
23
|
+
constructor(message?: string);
|
|
24
24
|
}
|
|
25
25
|
export declare class MissingTokenError extends Error {
|
|
26
26
|
readonly tokenType: "Access" | "Refresh";
|
package/build/errors.js
CHANGED
|
@@ -50,8 +50,8 @@ class UserNotFoundError extends Error {
|
|
|
50
50
|
}
|
|
51
51
|
exports.UserNotFoundError = UserNotFoundError;
|
|
52
52
|
class InvalidCredentialsError extends Error {
|
|
53
|
-
constructor() {
|
|
54
|
-
super(
|
|
53
|
+
constructor(message) {
|
|
54
|
+
super(`Invalid credentials: ${message || 'Authentication failed.'}`);
|
|
55
55
|
this.name = "InvalidCredentialsError";
|
|
56
56
|
}
|
|
57
57
|
}
|
package/build/index.d.ts
CHANGED
package/build/index.js
CHANGED
|
@@ -2,8 +2,8 @@ import * as Soap from "@soapjs/soap";
|
|
|
2
2
|
import { AuthThrottleConfig } from "../types";
|
|
3
3
|
export declare class AuthThrottleService {
|
|
4
4
|
private config;
|
|
5
|
-
private logger
|
|
6
|
-
constructor(config: AuthThrottleConfig, logger
|
|
5
|
+
private logger?;
|
|
6
|
+
constructor(config: AuthThrottleConfig, logger?: Soap.Logger);
|
|
7
7
|
checkFailedAttempts(identifier: string): Promise<void>;
|
|
8
8
|
incrementFailedAttempts(account: any): Promise<void>;
|
|
9
9
|
resetFailedAttempts(account: any): Promise<void>;
|
|
@@ -17,11 +17,11 @@ class AuthThrottleService {
|
|
|
17
17
|
(await this.config.getFailedAttempts?.(identifier)) || 0;
|
|
18
18
|
}
|
|
19
19
|
catch (e) {
|
|
20
|
-
this.logger
|
|
20
|
+
this.logger?.error("Check failed attempts:", e);
|
|
21
21
|
}
|
|
22
22
|
if (Number.isInteger(failedAttempts) &&
|
|
23
23
|
failedAttempts >= this.config.maxFailedAttempts) {
|
|
24
|
-
this.logger
|
|
24
|
+
this.logger?.warn(`User ${identifier} is temporarily locked out.`);
|
|
25
25
|
throw new errors_1.AccountLockedError();
|
|
26
26
|
}
|
|
27
27
|
}
|
package/build/services/index.js
CHANGED
|
@@ -22,3 +22,4 @@ __exportStar(require("./password.service"), exports);
|
|
|
22
22
|
__exportStar(require("./pkce.service"), exports);
|
|
23
23
|
__exportStar(require("./rate-limit.service"), exports);
|
|
24
24
|
__exportStar(require("./role.service"), exports);
|
|
25
|
+
__exportStar(require("./totp.service"), exports);
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import * as Soap from "@soapjs/soap";
|
|
2
|
-
import { PasswordPolicyConfig } from "../types";
|
|
2
|
+
import { NewPasswordOptions, PasswordInfo, PasswordPolicyConfig } from "../types";
|
|
3
3
|
export declare class PasswordService {
|
|
4
4
|
private config;
|
|
5
5
|
private logger;
|
|
6
6
|
constructor(config: PasswordPolicyConfig, logger: Soap.Logger);
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
private validateConfig;
|
|
8
|
+
validatePassword(password: string, previousPassword?: string): Promise<void>;
|
|
9
|
+
getPasswordPasswordInfo(identifier: string): Promise<PasswordInfo>;
|
|
9
10
|
generateResetToken(identifier: string): Promise<string>;
|
|
10
11
|
sendResetEmail(identifier: string, token: string): Promise<void>;
|
|
11
|
-
validateResetToken(token: string): Promise<
|
|
12
|
-
updatePassword(identifier: string, newPassword: string): Promise<void>;
|
|
12
|
+
validateResetToken(token: string): Promise<void>;
|
|
13
|
+
updatePassword(identifier: string, newPassword: string, passwordOptions?: NewPasswordOptions): Promise<void>;
|
|
13
14
|
isPasswordChangeRequired(identifier: string): Promise<boolean>;
|
|
15
|
+
generatePassword(identifier: string, options: NewPasswordOptions): Promise<string>;
|
|
14
16
|
}
|
|
@@ -22,57 +22,115 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
22
22
|
__setModuleDefault(result, mod);
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
25
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
29
|
exports.PasswordService = void 0;
|
|
27
30
|
const Soap = __importStar(require("@soapjs/soap"));
|
|
31
|
+
const bcrypt_1 = __importDefault(require("bcrypt"));
|
|
32
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
33
|
+
const validation_1 = require("../utils/validation");
|
|
34
|
+
const errors_1 = require("../errors");
|
|
28
35
|
class PasswordService {
|
|
29
36
|
config;
|
|
30
37
|
logger;
|
|
31
38
|
constructor(config, logger) {
|
|
32
39
|
this.config = config;
|
|
33
40
|
this.logger = logger;
|
|
41
|
+
this.validateConfig(config);
|
|
34
42
|
}
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
validateConfig(config) {
|
|
44
|
+
try {
|
|
45
|
+
validation_1.ValidationUtils.required(config, "config");
|
|
46
|
+
validation_1.ValidationUtils.object(config, "config");
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (error instanceof validation_1.ValidationError) {
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
throw new validation_1.ValidationError(`Invalid PasswordService configuration: ${error.message}`);
|
|
53
|
+
}
|
|
37
54
|
}
|
|
38
|
-
async
|
|
39
|
-
|
|
55
|
+
async validatePassword(password, previousPassword) {
|
|
56
|
+
validation_1.ValidationUtils.nonEmptyString(password, "password");
|
|
57
|
+
if (previousPassword !== undefined) {
|
|
58
|
+
validation_1.ValidationUtils.nonEmptyString(previousPassword, "previousPassword");
|
|
59
|
+
}
|
|
60
|
+
return this.config.validatePassword?.(password, previousPassword);
|
|
61
|
+
}
|
|
62
|
+
async getPasswordPasswordInfo(identifier) {
|
|
63
|
+
validation_1.ValidationUtils.nonEmptyString(identifier, "identifier");
|
|
64
|
+
return this.config.getPasswordPasswordInfo?.(identifier);
|
|
40
65
|
}
|
|
41
66
|
async generateResetToken(identifier) {
|
|
67
|
+
validation_1.ValidationUtils.nonEmptyString(identifier, "identifier");
|
|
42
68
|
if (!this.config?.generateResetToken) {
|
|
43
69
|
throw new Soap.NotImplementedError("generateResetToken");
|
|
44
70
|
}
|
|
45
71
|
return this.config.generateResetToken?.(identifier);
|
|
46
72
|
}
|
|
47
73
|
async sendResetEmail(identifier, token) {
|
|
48
|
-
|
|
74
|
+
validation_1.ValidationUtils.nonEmptyString(identifier, "identifier");
|
|
75
|
+
validation_1.ValidationUtils.nonEmptyString(token, "token");
|
|
76
|
+
if (!this.config?.sendPasswordResetEmail) {
|
|
49
77
|
throw new Soap.NotImplementedError("sendResetEmail");
|
|
50
78
|
}
|
|
51
|
-
return this.config.
|
|
79
|
+
return this.config.sendPasswordResetEmail?.(identifier, token);
|
|
52
80
|
}
|
|
53
81
|
async validateResetToken(token) {
|
|
54
|
-
|
|
82
|
+
validation_1.ValidationUtils.nonEmptyString(token, "token");
|
|
83
|
+
if (!this.config?.validatePasswordResetToken) {
|
|
55
84
|
throw new Soap.NotImplementedError("validateResetToken");
|
|
56
85
|
}
|
|
57
|
-
|
|
86
|
+
const isValid = await this.config.validatePasswordResetToken(token);
|
|
87
|
+
if (isValid === false) {
|
|
88
|
+
throw new errors_1.ExpiredResetTokenError();
|
|
89
|
+
}
|
|
58
90
|
}
|
|
59
|
-
async updatePassword(identifier, newPassword) {
|
|
91
|
+
async updatePassword(identifier, newPassword, passwordOptions) {
|
|
92
|
+
validation_1.ValidationUtils.nonEmptyString(identifier, "identifier");
|
|
93
|
+
validation_1.ValidationUtils.nonEmptyString(newPassword, "newPassword");
|
|
94
|
+
if (passwordOptions) {
|
|
95
|
+
validation_1.ValidationUtils.object(passwordOptions, "passwordOptions");
|
|
96
|
+
if (passwordOptions.type) {
|
|
97
|
+
validation_1.ValidationUtils.oneOf(passwordOptions.type, "passwordOptions.type", ["default", "one-time", "temporary"]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
60
100
|
if (!this.config?.updatePassword) {
|
|
61
101
|
throw new Soap.NotImplementedError("updatePassword");
|
|
62
102
|
}
|
|
63
|
-
return this.config.updatePassword?.(identifier, newPassword);
|
|
103
|
+
return this.config.updatePassword?.(identifier, newPassword, passwordOptions);
|
|
64
104
|
}
|
|
65
105
|
async isPasswordChangeRequired(identifier) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
106
|
+
validation_1.ValidationUtils.nonEmptyString(identifier, "identifier");
|
|
107
|
+
const passwordInfo = await this.config.getPasswordPasswordInfo?.(identifier);
|
|
108
|
+
if (passwordInfo &&
|
|
109
|
+
(passwordInfo.type === "one-time" ||
|
|
110
|
+
(passwordInfo.type === "temporary" &&
|
|
111
|
+
Number.isInteger(passwordInfo.expiresIn) &&
|
|
112
|
+
Date.now() - passwordInfo.lastChangeDate.getTime() >
|
|
113
|
+
passwordInfo.expiresIn))) {
|
|
114
|
+
return true;
|
|
74
115
|
}
|
|
75
116
|
return false;
|
|
76
117
|
}
|
|
118
|
+
async generatePassword(identifier, options) {
|
|
119
|
+
validation_1.ValidationUtils.nonEmptyString(identifier, "identifier");
|
|
120
|
+
validation_1.ValidationUtils.required(options, "options");
|
|
121
|
+
validation_1.ValidationUtils.object(options, "options");
|
|
122
|
+
validation_1.ValidationUtils.oneOf(options.type, "options.type", ["default", "one-time", "temporary"]);
|
|
123
|
+
let plaintext;
|
|
124
|
+
if (this.config.generatePassword) {
|
|
125
|
+
plaintext = await this.config.generatePassword(identifier, options);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
plaintext = crypto_1.default.randomBytes(12).toString("base64url");
|
|
129
|
+
}
|
|
130
|
+
const salt = await bcrypt_1.default.genSalt(10);
|
|
131
|
+
const hashedPassword = await bcrypt_1.default.hash(plaintext, salt);
|
|
132
|
+
await this.updatePassword(identifier, hashedPassword, options);
|
|
133
|
+
return plaintext;
|
|
134
|
+
}
|
|
77
135
|
}
|
|
78
136
|
exports.PasswordService = PasswordService;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface TotpOptions {
|
|
2
|
+
step?: number;
|
|
3
|
+
digits?: number;
|
|
4
|
+
window?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare class TotpService {
|
|
7
|
+
private readonly step;
|
|
8
|
+
private readonly digits;
|
|
9
|
+
private readonly window;
|
|
10
|
+
constructor(options?: TotpOptions);
|
|
11
|
+
generateSecret(byteLength?: number): string;
|
|
12
|
+
generateOTP(secret: string, timestamp?: number): string;
|
|
13
|
+
verifyOTP(secret: string, code: string, timestamp?: number): boolean;
|
|
14
|
+
generateQRUri(secret: string, issuer: string, account: string): string;
|
|
15
|
+
generateBackupCodes(count?: number): string[];
|
|
16
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TotpService = void 0;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
const BASE32_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
6
|
+
function base32Encode(buf) {
|
|
7
|
+
let bits = 0;
|
|
8
|
+
let value = 0;
|
|
9
|
+
let output = "";
|
|
10
|
+
for (const byte of buf) {
|
|
11
|
+
value = (value << 8) | byte;
|
|
12
|
+
bits += 8;
|
|
13
|
+
while (bits >= 5) {
|
|
14
|
+
output += BASE32_CHARS[(value >>> (bits - 5)) & 31];
|
|
15
|
+
bits -= 5;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
if (bits > 0) {
|
|
19
|
+
output += BASE32_CHARS[(value << (5 - bits)) & 31];
|
|
20
|
+
}
|
|
21
|
+
return output;
|
|
22
|
+
}
|
|
23
|
+
function base32Decode(str) {
|
|
24
|
+
const s = str.replace(/=+$/, "").toUpperCase();
|
|
25
|
+
const bytes = [];
|
|
26
|
+
let bits = 0;
|
|
27
|
+
let value = 0;
|
|
28
|
+
for (const ch of s) {
|
|
29
|
+
const idx = BASE32_CHARS.indexOf(ch);
|
|
30
|
+
if (idx === -1)
|
|
31
|
+
throw new Error(`Invalid base32 character: ${ch}`);
|
|
32
|
+
value = (value << 5) | idx;
|
|
33
|
+
bits += 5;
|
|
34
|
+
if (bits >= 8) {
|
|
35
|
+
bytes.push((value >>> (bits - 8)) & 255);
|
|
36
|
+
bits -= 8;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return Buffer.from(bytes);
|
|
40
|
+
}
|
|
41
|
+
function hotp(key, counter, digits) {
|
|
42
|
+
const buf = Buffer.alloc(8);
|
|
43
|
+
buf.writeBigUInt64BE(counter);
|
|
44
|
+
const hmac = (0, crypto_1.createHmac)("sha1", key).update(buf).digest();
|
|
45
|
+
const offset = hmac[hmac.length - 1] & 0x0f;
|
|
46
|
+
const code = ((hmac[offset] & 0x7f) << 24) |
|
|
47
|
+
((hmac[offset + 1] & 0xff) << 16) |
|
|
48
|
+
((hmac[offset + 2] & 0xff) << 8) |
|
|
49
|
+
(hmac[offset + 3] & 0xff);
|
|
50
|
+
return String(code % 10 ** digits).padStart(digits, "0");
|
|
51
|
+
}
|
|
52
|
+
class TotpService {
|
|
53
|
+
step;
|
|
54
|
+
digits;
|
|
55
|
+
window;
|
|
56
|
+
constructor(options = {}) {
|
|
57
|
+
this.step = options.step ?? 30;
|
|
58
|
+
this.digits = options.digits ?? 6;
|
|
59
|
+
this.window = options.window ?? 1;
|
|
60
|
+
}
|
|
61
|
+
generateSecret(byteLength = 20) {
|
|
62
|
+
return base32Encode((0, crypto_1.randomBytes)(byteLength));
|
|
63
|
+
}
|
|
64
|
+
generateOTP(secret, timestamp = Date.now()) {
|
|
65
|
+
const key = base32Decode(secret);
|
|
66
|
+
const counter = BigInt(Math.floor(timestamp / 1000 / this.step));
|
|
67
|
+
return hotp(key, counter, this.digits);
|
|
68
|
+
}
|
|
69
|
+
verifyOTP(secret, code, timestamp = Date.now()) {
|
|
70
|
+
if (!/^\d+$/.test(code) || code.length !== this.digits)
|
|
71
|
+
return false;
|
|
72
|
+
const key = base32Decode(secret);
|
|
73
|
+
const counter = BigInt(Math.floor(timestamp / 1000 / this.step));
|
|
74
|
+
for (let delta = -this.window; delta <= this.window; delta++) {
|
|
75
|
+
const expected = hotp(key, counter + BigInt(delta), this.digits);
|
|
76
|
+
if (expected === code)
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
generateQRUri(secret, issuer, account) {
|
|
82
|
+
const params = new URLSearchParams({
|
|
83
|
+
secret,
|
|
84
|
+
issuer,
|
|
85
|
+
algorithm: "SHA1",
|
|
86
|
+
digits: String(this.digits),
|
|
87
|
+
period: String(this.step),
|
|
88
|
+
});
|
|
89
|
+
const label = `${encodeURIComponent(issuer)}:${encodeURIComponent(account)}`;
|
|
90
|
+
return `otpauth://totp/${label}?${params.toString()}`;
|
|
91
|
+
}
|
|
92
|
+
generateBackupCodes(count = 8) {
|
|
93
|
+
return Array.from({ length: count }, () => (0, crypto_1.randomBytes)(6).toString("hex").toUpperCase());
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
exports.TotpService = TotpService;
|
|
@@ -7,6 +7,7 @@ export declare class SessionHandler<TContext = unknown, TUser = unknown, TData =
|
|
|
7
7
|
private sessionKey;
|
|
8
8
|
private headerKey;
|
|
9
9
|
constructor(config: SessionConfig, logger?: Soap.Logger);
|
|
10
|
+
private validateConfig;
|
|
10
11
|
setSessionId(context: TContext, sessionId: string): void;
|
|
11
12
|
getSessionId(context: TContext): string | null;
|
|
12
13
|
generateSessionId(): string;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SessionHandler = void 0;
|
|
4
|
-
const
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
5
|
const session_errors_1 = require("./session.errors");
|
|
6
|
+
const validation_1 = require("../utils/validation");
|
|
6
7
|
class SessionHandler {
|
|
7
8
|
config;
|
|
8
9
|
logger;
|
|
@@ -12,6 +13,7 @@ class SessionHandler {
|
|
|
12
13
|
constructor(config, logger) {
|
|
13
14
|
this.config = config;
|
|
14
15
|
this.logger = logger;
|
|
16
|
+
this.validateConfig(config);
|
|
15
17
|
if (!config.store) {
|
|
16
18
|
throw new Error("Session store is required.");
|
|
17
19
|
}
|
|
@@ -19,8 +21,37 @@ class SessionHandler {
|
|
|
19
21
|
this.sessionKey = config.sessionKey || "SESSIONID";
|
|
20
22
|
this.headerKey = config.sessionHeader || "x-session-id";
|
|
21
23
|
}
|
|
24
|
+
validateConfig(config) {
|
|
25
|
+
try {
|
|
26
|
+
validation_1.ValidationUtils.required(config, "config");
|
|
27
|
+
validation_1.ValidationUtils.required(config.secret, "config.secret");
|
|
28
|
+
validation_1.ValidationUtils.nonEmptyString(config.secret, "config.secret");
|
|
29
|
+
validation_1.ValidationUtils.required(config.store, "config.store");
|
|
30
|
+
validation_1.ValidationUtils.object(config.store, "config.store");
|
|
31
|
+
if (config.sessionKey) {
|
|
32
|
+
validation_1.ValidationUtils.nonEmptyString(config.sessionKey, "config.sessionKey");
|
|
33
|
+
}
|
|
34
|
+
if (config.sessionHeader) {
|
|
35
|
+
validation_1.ValidationUtils.nonEmptyString(config.sessionHeader, "config.sessionHeader");
|
|
36
|
+
}
|
|
37
|
+
const requiredMethods = ['getSession', 'setSession', 'destroySession', 'touchSession', 'getSessionIds'];
|
|
38
|
+
for (const method of requiredMethods) {
|
|
39
|
+
if (typeof config.store[method] !== 'function') {
|
|
40
|
+
throw new validation_1.ValidationError(`Session store must implement ${method} method`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (error instanceof validation_1.ValidationError) {
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
throw new validation_1.ValidationError(`Invalid SessionHandler configuration: ${error.message}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
22
51
|
setSessionId(context, sessionId) {
|
|
23
52
|
try {
|
|
53
|
+
validation_1.ValidationUtils.required(context, "context");
|
|
54
|
+
validation_1.ValidationUtils.nonEmptyString(sessionId, "sessionId");
|
|
24
55
|
if (this.config.embedSessionId) {
|
|
25
56
|
this.config.embedSessionId(context, sessionId);
|
|
26
57
|
return;
|
|
@@ -48,10 +79,12 @@ class SessionHandler {
|
|
|
48
79
|
}
|
|
49
80
|
catch (error) {
|
|
50
81
|
this.config.logger?.error("Error setting session ID:", error);
|
|
82
|
+
throw error;
|
|
51
83
|
}
|
|
52
84
|
}
|
|
53
85
|
getSessionId(context) {
|
|
54
86
|
try {
|
|
87
|
+
validation_1.ValidationUtils.required(context, "context");
|
|
55
88
|
if (this.config.getSessionId) {
|
|
56
89
|
return this.config.getSessionId(context);
|
|
57
90
|
}
|
|
@@ -74,7 +107,7 @@ class SessionHandler {
|
|
|
74
107
|
}
|
|
75
108
|
}
|
|
76
109
|
generateSessionId() {
|
|
77
|
-
return this.config.generateSessionId?.() || (0,
|
|
110
|
+
return this.config.generateSessionId?.() || (0, crypto_1.randomUUID)();
|
|
78
111
|
}
|
|
79
112
|
buildSessionData(data, context) {
|
|
80
113
|
return this.config.createSessionData
|
|
@@ -83,6 +116,7 @@ class SessionHandler {
|
|
|
83
116
|
}
|
|
84
117
|
async getSessionData(sessionId) {
|
|
85
118
|
try {
|
|
119
|
+
validation_1.ValidationUtils.nonEmptyString(sessionId, "sessionId");
|
|
86
120
|
const data = await this.store.getSession(sessionId);
|
|
87
121
|
return data;
|
|
88
122
|
}
|
|
@@ -93,15 +127,14 @@ class SessionHandler {
|
|
|
93
127
|
}
|
|
94
128
|
async setSessionData(sessionId, data) {
|
|
95
129
|
try {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
130
|
+
validation_1.ValidationUtils.nonEmptyString(sessionId, "sessionId");
|
|
131
|
+
validation_1.ValidationUtils.required(data, "data");
|
|
100
132
|
await this.store.setSession(sessionId, data);
|
|
101
133
|
this.config.logger?.info(`Session set for ID: ${sessionId}`);
|
|
102
134
|
}
|
|
103
135
|
catch (error) {
|
|
104
136
|
this.config.logger?.error("Error storing session data:", error);
|
|
137
|
+
throw error;
|
|
105
138
|
}
|
|
106
139
|
}
|
|
107
140
|
async touch(sessionId, data) {
|
package/build/soap-auth.d.ts
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Soap from "@soapjs/soap";
|
|
2
|
+
import { AuthCategories, SoapAuthConfig } from "./types";
|
|
2
3
|
export declare class SoapAuth {
|
|
3
4
|
private requiredStrategyMethods;
|
|
4
5
|
private strategies;
|
|
5
6
|
private logger?;
|
|
6
7
|
constructor(config: SoapAuthConfig);
|
|
8
|
+
private validateConfig;
|
|
9
|
+
private validateSessionConfig;
|
|
10
|
+
private validateJwtConfig;
|
|
11
|
+
private validateHttpStrategies;
|
|
12
|
+
private validateSocketStrategies;
|
|
7
13
|
private isAuthStrategy;
|
|
8
|
-
addStrategy(strategyInstance: AuthStrategy | undefined, name: string, type: AuthCategories): void;
|
|
14
|
+
addStrategy(strategyInstance: Soap.AuthStrategy | undefined, name: string, type: AuthCategories): void;
|
|
9
15
|
removeStrategy(name: string | string[], type: AuthCategories): void;
|
|
10
16
|
hasStrategy(name: string, type: AuthCategories): boolean;
|
|
11
|
-
getStrategy<T extends AuthStrategy>(name: string, type: AuthCategories): T;
|
|
12
|
-
getHttpStrategy<T extends AuthStrategy>(name: string): T;
|
|
13
|
-
getSocketStrategy<T extends AuthStrategy>(name: string): T;
|
|
14
|
-
getEventStrategy<T extends AuthStrategy>(name: string): T;
|
|
17
|
+
getStrategy<T extends Soap.AuthStrategy>(name: string, type: AuthCategories): T;
|
|
18
|
+
getHttpStrategy<T extends Soap.AuthStrategy>(name: string): T;
|
|
19
|
+
getSocketStrategy<T extends Soap.AuthStrategy>(name: string): T;
|
|
20
|
+
getEventStrategy<T extends Soap.AuthStrategy>(name: string): T;
|
|
15
21
|
listStrategies(type: AuthCategories): string[];
|
|
16
22
|
init(sequential?: boolean): Promise<void>;
|
|
23
|
+
static create(config: SoapAuthConfig): Promise<SoapAuth>;
|
|
17
24
|
}
|