hi-secure 1.0.15 → 1.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/ArgonAdapter.d.ts +1 -1
- package/dist/adapters/ArgonAdapter.d.ts.map +1 -1
- package/dist/adapters/ArgonAdapter.js +43 -5
- package/dist/adapters/ArgonAdapter.js.map +1 -1
- package/dist/adapters/BcryptAdapter.d.ts.map +1 -1
- package/dist/adapters/BcryptAdapter.js +43 -3
- package/dist/adapters/BcryptAdapter.js.map +1 -1
- package/dist/adapters/ExpressRLAdapter.d.ts.map +1 -1
- package/dist/adapters/ExpressRLAdapter.js +48 -6
- package/dist/adapters/ExpressRLAdapter.js.map +1 -1
- package/dist/adapters/ExpressValidatorAdapter.d.ts.map +1 -1
- package/dist/adapters/ExpressValidatorAdapter.js +50 -10
- package/dist/adapters/ExpressValidatorAdapter.js.map +1 -1
- package/dist/adapters/GoogleAdapter.d.ts.map +1 -1
- package/dist/adapters/GoogleAdapter.js +82 -16
- package/dist/adapters/GoogleAdapter.js.map +1 -1
- package/dist/adapters/JWTAdapter.d.ts.map +1 -1
- package/dist/adapters/JWTAdapter.js +104 -15
- package/dist/adapters/JWTAdapter.js.map +1 -1
- package/dist/adapters/RLFlexibleAdapter.d.ts.map +1 -1
- package/dist/adapters/RLFlexibleAdapter.js +87 -12
- package/dist/adapters/RLFlexibleAdapter.js.map +1 -1
- package/dist/adapters/SanitizeHtmlAdapter.d.ts.map +1 -1
- package/dist/adapters/SanitizeHtmlAdapter.js +81 -13
- package/dist/adapters/SanitizeHtmlAdapter.js.map +1 -1
- package/dist/adapters/XSSAdapter.d.ts +1 -1
- package/dist/adapters/XSSAdapter.d.ts.map +1 -1
- package/dist/adapters/XSSAdapter.js +137 -20
- package/dist/adapters/XSSAdapter.js.map +1 -1
- package/dist/adapters/ZodAdapter.d.ts +1 -1
- package/dist/adapters/ZodAdapter.d.ts.map +1 -1
- package/dist/adapters/ZodAdapter.js +13 -8
- package/dist/adapters/ZodAdapter.js.map +1 -1
- package/dist/core/HiSecure.d.ts +3 -4
- package/dist/core/HiSecure.d.ts.map +1 -1
- package/dist/core/HiSecure.js +108 -121
- package/dist/core/HiSecure.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/logging/index.d.ts.map +1 -1
- package/dist/logging/index.js +2 -0
- package/dist/logging/index.js.map +1 -1
- package/dist/logging/morganSetup.d.ts.map +1 -1
- package/dist/logging/morganSetup.js +22 -1
- package/dist/logging/morganSetup.js.map +1 -1
- package/dist/logging/winstonSetup.d.ts.map +1 -1
- package/dist/logging/winstonSetup.js +61 -3
- package/dist/logging/winstonSetup.js.map +1 -1
- package/dist/managers/AuthManager.d.ts +2 -2
- package/dist/managers/AuthManager.d.ts.map +1 -1
- package/dist/managers/AuthManager.js +167 -31
- package/dist/managers/AuthManager.js.map +1 -1
- package/dist/managers/CorsManager.d.ts.map +1 -1
- package/dist/managers/CorsManager.js +46 -11
- package/dist/managers/CorsManager.js.map +1 -1
- package/dist/managers/HashManager.d.ts +1 -1
- package/dist/managers/HashManager.d.ts.map +1 -1
- package/dist/managers/HashManager.js +127 -17
- package/dist/managers/HashManager.js.map +1 -1
- package/dist/managers/JsonManager.d.ts +1 -1
- package/dist/managers/JsonManager.d.ts.map +1 -1
- package/dist/managers/JsonManager.js +99 -16
- package/dist/managers/JsonManager.js.map +1 -1
- package/dist/managers/RateLimitManager.d.ts +1 -1
- package/dist/managers/RateLimitManager.d.ts.map +1 -1
- package/dist/managers/RateLimitManager.js +46 -22
- package/dist/managers/RateLimitManager.js.map +1 -1
- package/dist/managers/SanitizerManager.d.ts.map +1 -1
- package/dist/managers/SanitizerManager.js +112 -15
- package/dist/managers/SanitizerManager.js.map +1 -1
- package/dist/managers/ValidatorManager.d.ts.map +1 -1
- package/dist/managers/ValidatorManager.js +90 -7
- package/dist/managers/ValidatorManager.js.map +1 -1
- package/package.json +2 -6
- package/readme.md +3 -6
- package/src/adapters/ArgonAdapter.ts +55 -6
- package/src/adapters/BcryptAdapter.ts +56 -8
- package/src/adapters/ExpressRLAdapter.ts +62 -9
- package/src/adapters/ExpressValidatorAdapter.ts +67 -11
- package/src/adapters/GoogleAdapter.ts +106 -21
- package/src/adapters/JWTAdapter.ts +129 -21
- package/src/adapters/RLFlexibleAdapter.ts +113 -16
- package/src/adapters/SanitizeHtmlAdapter.ts +111 -18
- package/src/adapters/XSSAdapter.ts +183 -39
- package/src/adapters/ZodAdapter.ts +56 -10
- package/src/core/HiSecure.ts +496 -162
- package/src/index.ts +4 -0
- package/src/logging/index.ts +6 -0
- package/src/logging/morganSetup.ts +36 -1
- package/src/logging/winstonSetup.ts +97 -8
- package/src/managers/AuthManager.ts +205 -34
- package/src/managers/CorsManager.ts +63 -16
- package/src/managers/HashManager.ts +156 -19
- package/src/managers/JsonManager.ts +119 -15
- package/src/managers/RateLimitManager.ts +174 -29
- package/src/managers/SanitizerManager.ts +150 -25
- package/src/managers/ValidatorManager.ts +115 -15
|
@@ -1,3 +1,52 @@
|
|
|
1
|
+
// import bcrypt from "bcryptjs";
|
|
2
|
+
// import { AdapterError } from "../core/errors/AdapterError";
|
|
3
|
+
// import { logger } from "../logging";
|
|
4
|
+
|
|
5
|
+
// export class BcryptAdapter {
|
|
6
|
+
// constructor(private saltRounds: number = 10) {}
|
|
7
|
+
|
|
8
|
+
// async hash(value: string): Promise<string> {
|
|
9
|
+
// try {
|
|
10
|
+
// if (typeof value !== "string") {
|
|
11
|
+
// throw new AdapterError("Value to hash must be a string.");
|
|
12
|
+
// }
|
|
13
|
+
|
|
14
|
+
// return await bcrypt.hash(value, this.saltRounds);
|
|
15
|
+
// } catch (err: any) {
|
|
16
|
+
// logger.error("Bcrypt hashing failed", {
|
|
17
|
+
// error: err?.message || err,
|
|
18
|
+
// saltRounds: this.saltRounds
|
|
19
|
+
// });
|
|
20
|
+
|
|
21
|
+
// throw new AdapterError("Bcrypt hashing failed.");
|
|
22
|
+
// }
|
|
23
|
+
// }
|
|
24
|
+
|
|
25
|
+
// async verify(value: string, hashed: string): Promise<boolean> {
|
|
26
|
+
// try {
|
|
27
|
+
// if (typeof value !== "string") {
|
|
28
|
+
// throw new AdapterError("Value to verify must be a string.");
|
|
29
|
+
// }
|
|
30
|
+
|
|
31
|
+
// if (!hashed || typeof hashed !== "string") {
|
|
32
|
+
// throw new AdapterError("Invalid hashed string provided.");
|
|
33
|
+
// }
|
|
34
|
+
|
|
35
|
+
// return await bcrypt.compare(value, hashed);
|
|
36
|
+
// } catch (err: any) {
|
|
37
|
+
// logger.error("Bcrypt verify failed", {
|
|
38
|
+
// error: err?.message || err
|
|
39
|
+
// });
|
|
40
|
+
|
|
41
|
+
// throw new AdapterError("Bcrypt verify failed.");
|
|
42
|
+
// }
|
|
43
|
+
// }
|
|
44
|
+
// }
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
1
50
|
import bcrypt from "bcryptjs";
|
|
2
51
|
import { AdapterError } from "../core/errors/AdapterError";
|
|
3
52
|
import { logger } from "../logging";
|
|
@@ -14,8 +63,10 @@ export class BcryptAdapter {
|
|
|
14
63
|
return await bcrypt.hash(value, this.saltRounds);
|
|
15
64
|
} catch (err: any) {
|
|
16
65
|
logger.error("Bcrypt hashing failed", {
|
|
17
|
-
|
|
18
|
-
|
|
66
|
+
adapter: "bcrypt",
|
|
67
|
+
operation: "hash",
|
|
68
|
+
saltRounds: this.saltRounds,
|
|
69
|
+
reason: err?.message
|
|
19
70
|
});
|
|
20
71
|
|
|
21
72
|
throw new AdapterError("Bcrypt hashing failed.");
|
|
@@ -35,15 +86,12 @@ export class BcryptAdapter {
|
|
|
35
86
|
return await bcrypt.compare(value, hashed);
|
|
36
87
|
} catch (err: any) {
|
|
37
88
|
logger.error("Bcrypt verify failed", {
|
|
38
|
-
|
|
89
|
+
adapter: "bcrypt",
|
|
90
|
+
operation: "verify",
|
|
91
|
+
reason: err?.message
|
|
39
92
|
});
|
|
40
93
|
|
|
41
94
|
throw new AdapterError("Bcrypt verify failed.");
|
|
42
95
|
}
|
|
43
96
|
}
|
|
44
97
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
@@ -1,6 +1,54 @@
|
|
|
1
|
+
// import rateLimit from "express-rate-limit";
|
|
2
|
+
// import { logger } from "../logging/index.js";
|
|
3
|
+
// import { AdapterError } from "../core/errors/AdapterError.js";
|
|
4
|
+
|
|
5
|
+
// export interface RateLimitOptions {
|
|
6
|
+
// windowMs?: number;
|
|
7
|
+
// max?: number;
|
|
8
|
+
// message?: any;
|
|
9
|
+
// skipFailedRequests?: boolean;
|
|
10
|
+
// standardHeaders?: boolean;
|
|
11
|
+
// legacyHeaders?: boolean;
|
|
12
|
+
// [key: string]: any;
|
|
13
|
+
// }
|
|
14
|
+
|
|
15
|
+
// export class ExpressRLAdapter {
|
|
16
|
+
// getMiddleware(options: RateLimitOptions = {}) {
|
|
17
|
+
// try {
|
|
18
|
+
// const defaultOptions = {
|
|
19
|
+
// windowMs: 15 * 60 * 1000,
|
|
20
|
+
// max: 100,
|
|
21
|
+
// message: { error: "Too many requests" },
|
|
22
|
+
// standardHeaders: true,
|
|
23
|
+
// legacyHeaders: false,
|
|
24
|
+
// skipFailedRequests: false
|
|
25
|
+
// };
|
|
26
|
+
|
|
27
|
+
// const finalOptions = { ...defaultOptions, ...options };
|
|
28
|
+
|
|
29
|
+
// const limiter = rateLimit(finalOptions);
|
|
30
|
+
|
|
31
|
+
// logger.debug("Express rate limiter configured", {
|
|
32
|
+
// windowMs: finalOptions.windowMs,
|
|
33
|
+
// max: finalOptions.max
|
|
34
|
+
// });
|
|
35
|
+
|
|
36
|
+
// return limiter;
|
|
37
|
+
|
|
38
|
+
// } catch (err: any) {
|
|
39
|
+
// logger.error("ExpressRLAdapter: failed to create limiter", {
|
|
40
|
+
// error: err?.message || err
|
|
41
|
+
// });
|
|
42
|
+
// throw new AdapterError("Express rate limiter creation failed.");
|
|
43
|
+
// }
|
|
44
|
+
// }
|
|
45
|
+
// }
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
1
49
|
import rateLimit from "express-rate-limit";
|
|
2
|
-
import { logger } from "../logging
|
|
3
|
-
import { AdapterError } from "../core/errors/AdapterError
|
|
50
|
+
import { logger } from "../logging";
|
|
51
|
+
import { AdapterError } from "../core/errors/AdapterError";
|
|
4
52
|
|
|
5
53
|
export interface RateLimitOptions {
|
|
6
54
|
windowMs?: number;
|
|
@@ -16,7 +64,7 @@ export class ExpressRLAdapter {
|
|
|
16
64
|
getMiddleware(options: RateLimitOptions = {}) {
|
|
17
65
|
try {
|
|
18
66
|
const defaultOptions = {
|
|
19
|
-
windowMs: 15 * 60 * 1000,
|
|
67
|
+
windowMs: 15 * 60 * 1000,
|
|
20
68
|
max: 100,
|
|
21
69
|
message: { error: "Too many requests" },
|
|
22
70
|
standardHeaders: true,
|
|
@@ -25,21 +73,26 @@ export class ExpressRLAdapter {
|
|
|
25
73
|
};
|
|
26
74
|
|
|
27
75
|
const finalOptions = { ...defaultOptions, ...options };
|
|
28
|
-
|
|
76
|
+
|
|
29
77
|
const limiter = rateLimit(finalOptions);
|
|
78
|
+
|
|
30
79
|
|
|
31
|
-
logger.
|
|
80
|
+
logger.info("Express rate limiter configured", {
|
|
81
|
+
adapter: "express-rate-limit",
|
|
82
|
+
operation: "configure",
|
|
32
83
|
windowMs: finalOptions.windowMs,
|
|
33
84
|
max: finalOptions.max
|
|
34
85
|
});
|
|
35
86
|
|
|
36
87
|
return limiter;
|
|
37
|
-
|
|
38
88
|
} catch (err: any) {
|
|
39
|
-
logger.error("
|
|
40
|
-
|
|
89
|
+
logger.error("Express rate limiter setup failed", {
|
|
90
|
+
adapter: "express-rate-limit",
|
|
91
|
+
operation: "configure",
|
|
92
|
+
reason: err?.message
|
|
41
93
|
});
|
|
94
|
+
|
|
42
95
|
throw new AdapterError("Express rate limiter creation failed.");
|
|
43
96
|
}
|
|
44
97
|
}
|
|
45
|
-
}
|
|
98
|
+
}
|
|
@@ -1,6 +1,56 @@
|
|
|
1
|
+
// import { validationResult } from "express-validator";
|
|
2
|
+
// import { ValidationError } from "../core/errors/ValidationError.js";
|
|
3
|
+
// import { logger } from "../logging/index.js";
|
|
4
|
+
|
|
5
|
+
// export class ExpressValidatorAdapter {
|
|
6
|
+
// private globalSchema?: any[];
|
|
7
|
+
|
|
8
|
+
// constructor(globalSchema?: any[]) {
|
|
9
|
+
// this.globalSchema = globalSchema;
|
|
10
|
+
// }
|
|
11
|
+
|
|
12
|
+
// validate(dynamicSchema?: any[]) {
|
|
13
|
+
// const schema = dynamicSchema || this.globalSchema;
|
|
14
|
+
|
|
15
|
+
// if (!schema || !Array.isArray(schema)) {
|
|
16
|
+
// return (req: any, res: any, next: any) => next();
|
|
17
|
+
// }
|
|
18
|
+
|
|
19
|
+
// return [
|
|
20
|
+
// ...schema,
|
|
21
|
+
|
|
22
|
+
// (req: any, res: any, next: any) => {
|
|
23
|
+
// const errors = validationResult(req);
|
|
24
|
+
|
|
25
|
+
// if (!errors.isEmpty()) {
|
|
26
|
+
// const formatted = errors.array().map(err => ({
|
|
27
|
+
// message: err.msg,
|
|
28
|
+
// // param: err.param ,
|
|
29
|
+
// // location: err.location
|
|
30
|
+
// }));
|
|
31
|
+
|
|
32
|
+
// logger.warn("express-validator failed", {
|
|
33
|
+
// path: req.path,
|
|
34
|
+
// method: req.method,
|
|
35
|
+
// errors: formatted,
|
|
36
|
+
// preview: JSON.stringify(req.body).slice(0, 200)
|
|
37
|
+
// });
|
|
38
|
+
|
|
39
|
+
// return next(new ValidationError("Validation failed.", formatted as any));
|
|
40
|
+
// }
|
|
41
|
+
|
|
42
|
+
// next();
|
|
43
|
+
// }
|
|
44
|
+
// ];
|
|
45
|
+
// }
|
|
46
|
+
// }
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
1
51
|
import { validationResult } from "express-validator";
|
|
2
|
-
import { ValidationError } from "../core/errors/ValidationError
|
|
3
|
-
import { logger } from "../logging
|
|
52
|
+
import { ValidationError } from "../core/errors/ValidationError";
|
|
53
|
+
import { logger } from "../logging";
|
|
4
54
|
|
|
5
55
|
export class ExpressValidatorAdapter {
|
|
6
56
|
private globalSchema?: any[];
|
|
@@ -23,24 +73,30 @@ export class ExpressValidatorAdapter {
|
|
|
23
73
|
const errors = validationResult(req);
|
|
24
74
|
|
|
25
75
|
if (!errors.isEmpty()) {
|
|
26
|
-
const
|
|
76
|
+
const formattedErrors = errors.array().map(err => ({
|
|
27
77
|
message: err.msg,
|
|
28
|
-
|
|
29
|
-
// location: err.location
|
|
78
|
+
field: err.type
|
|
30
79
|
}));
|
|
31
80
|
|
|
32
|
-
logger.warn("
|
|
33
|
-
|
|
81
|
+
logger.warn("Request validation failed", {
|
|
82
|
+
adapter: "express-validator",
|
|
83
|
+
operation: "validate",
|
|
34
84
|
method: req.method,
|
|
35
|
-
|
|
36
|
-
|
|
85
|
+
path: req.path,
|
|
86
|
+
errorCount: formattedErrors.length,
|
|
87
|
+
errors: formattedErrors,
|
|
88
|
+
bodyPreview: req.body
|
|
89
|
+
? JSON.stringify(req.body).slice(0, 150)
|
|
90
|
+
: undefined
|
|
37
91
|
});
|
|
38
92
|
|
|
39
|
-
return next(
|
|
93
|
+
return next(
|
|
94
|
+
new ValidationError("Validation failed.", formattedErrors as any)
|
|
95
|
+
);
|
|
40
96
|
}
|
|
41
97
|
|
|
42
98
|
next();
|
|
43
99
|
}
|
|
44
100
|
];
|
|
45
101
|
}
|
|
46
|
-
}
|
|
102
|
+
}
|
|
@@ -1,7 +1,88 @@
|
|
|
1
|
-
import { OAuth2Client, LoginTicket } from "google-auth-library";
|
|
2
|
-
import { AdapterError } from "../core/errors/AdapterError.js";
|
|
1
|
+
// import { OAuth2Client, LoginTicket } from "google-auth-library";
|
|
2
|
+
// import { AdapterError } from "../core/errors/AdapterError.js";
|
|
3
|
+
|
|
4
|
+
// import {logger} from '../logging';
|
|
5
|
+
|
|
6
|
+
// export interface GoogleTokenPayload {
|
|
7
|
+
// sub: string;
|
|
8
|
+
// email: string;
|
|
9
|
+
// email_verified: boolean;
|
|
10
|
+
// name?: string;
|
|
11
|
+
// picture?: string;
|
|
12
|
+
// [key: string]: any;
|
|
13
|
+
// }
|
|
14
|
+
|
|
15
|
+
// export class GoogleAdapter {
|
|
16
|
+
// private client: OAuth2Client;
|
|
17
|
+
// private clientId?: string;
|
|
18
|
+
|
|
19
|
+
// constructor(clientId?: string) {
|
|
20
|
+
// if (clientId && clientId.trim().length === 0) {
|
|
21
|
+
// throw new AdapterError("Google clientId cannot be empty string");
|
|
22
|
+
// }
|
|
23
|
+
|
|
24
|
+
// this.client = new OAuth2Client(clientId);
|
|
25
|
+
// this.clientId = clientId;
|
|
26
|
+
// }
|
|
27
|
+
|
|
28
|
+
// async verifyIdToken(idToken: string): Promise<GoogleTokenPayload> {
|
|
29
|
+
// try {
|
|
30
|
+
// if (!idToken || typeof idToken !== 'string') {
|
|
31
|
+
// throw new AdapterError("Invalid ID token provided");
|
|
32
|
+
// }
|
|
33
|
+
|
|
34
|
+
// const options: { idToken: string; audience?: string | string[] } = {
|
|
35
|
+
// idToken
|
|
36
|
+
// };
|
|
37
|
+
|
|
38
|
+
// // audience only if clientId is provided and not empty
|
|
39
|
+
// if (this.clientId && this.clientId.trim().length > 0) {
|
|
40
|
+
// options.audience = this.clientId;
|
|
41
|
+
// }
|
|
42
|
+
|
|
43
|
+
// const ticket: LoginTicket = await this.client.verifyIdToken(options);
|
|
44
|
+
// const payload = ticket.getPayload();
|
|
45
|
+
|
|
46
|
+
// if (!payload) {
|
|
47
|
+
// logger.warn("GoogleAdapter: Empty payload");
|
|
48
|
+
// throw new AdapterError("Invalid Google ID token payload.");
|
|
49
|
+
// }
|
|
50
|
+
|
|
51
|
+
// // result object
|
|
52
|
+
// const result: GoogleTokenPayload = {
|
|
53
|
+
// sub: payload.sub,
|
|
54
|
+
// email: payload.email || '',
|
|
55
|
+
// email_verified: payload.email_verified || false,
|
|
56
|
+
// name: payload.name,
|
|
57
|
+
// picture: payload.picture
|
|
58
|
+
// };
|
|
59
|
+
|
|
60
|
+
// // remaining properties from payload
|
|
61
|
+
// const { sub, email, email_verified, name, picture, ...rest } = payload;
|
|
62
|
+
// Object.assign(result, rest);
|
|
63
|
+
|
|
64
|
+
// return result;
|
|
65
|
+
|
|
66
|
+
// } catch (err: any) {
|
|
67
|
+
// logger.error("GoogleAdapter.verifyIdToken failed", {
|
|
68
|
+
// error: err?.message,
|
|
69
|
+
// hasClientId: !!this.clientId
|
|
70
|
+
// });
|
|
71
|
+
|
|
72
|
+
// if (err.message?.includes('audience')) {
|
|
73
|
+
// throw new AdapterError("Invalid Google client ID configured.");
|
|
74
|
+
// }
|
|
75
|
+
|
|
76
|
+
// throw new AdapterError(err?.message || "Google token verification failed");
|
|
77
|
+
// }
|
|
78
|
+
// }
|
|
79
|
+
// }
|
|
80
|
+
|
|
81
|
+
|
|
3
82
|
|
|
4
|
-
import {
|
|
83
|
+
import { OAuth2Client, LoginTicket } from "google-auth-library";
|
|
84
|
+
import { AdapterError } from "../core/errors/AdapterError";
|
|
85
|
+
import { logger } from "../logging";
|
|
5
86
|
|
|
6
87
|
export interface GoogleTokenPayload {
|
|
7
88
|
sub: string;
|
|
@@ -20,60 +101,64 @@ export class GoogleAdapter {
|
|
|
20
101
|
if (clientId && clientId.trim().length === 0) {
|
|
21
102
|
throw new AdapterError("Google clientId cannot be empty string");
|
|
22
103
|
}
|
|
23
|
-
|
|
104
|
+
|
|
24
105
|
this.client = new OAuth2Client(clientId);
|
|
25
106
|
this.clientId = clientId;
|
|
26
107
|
}
|
|
27
108
|
|
|
28
109
|
async verifyIdToken(idToken: string): Promise<GoogleTokenPayload> {
|
|
29
110
|
try {
|
|
30
|
-
if (!idToken || typeof idToken !==
|
|
111
|
+
if (!idToken || typeof idToken !== "string") {
|
|
31
112
|
throw new AdapterError("Invalid ID token provided");
|
|
32
113
|
}
|
|
33
114
|
|
|
34
|
-
const options: { idToken: string; audience?: string | string[] } = {
|
|
35
|
-
idToken
|
|
115
|
+
const options: { idToken: string; audience?: string | string[] } = {
|
|
116
|
+
idToken
|
|
36
117
|
};
|
|
37
118
|
|
|
38
|
-
// audience only if clientId is provided and not empty
|
|
39
119
|
if (this.clientId && this.clientId.trim().length > 0) {
|
|
40
120
|
options.audience = this.clientId;
|
|
41
121
|
}
|
|
42
122
|
|
|
43
123
|
const ticket: LoginTicket = await this.client.verifyIdToken(options);
|
|
44
124
|
const payload = ticket.getPayload();
|
|
45
|
-
|
|
125
|
+
|
|
46
126
|
if (!payload) {
|
|
47
|
-
logger.warn("
|
|
127
|
+
logger.warn("Google ID token payload empty", {
|
|
128
|
+
adapter: "google-auth",
|
|
129
|
+
operation: "verifyIdToken",
|
|
130
|
+
hasClientId: !!this.clientId
|
|
131
|
+
});
|
|
132
|
+
|
|
48
133
|
throw new AdapterError("Invalid Google ID token payload.");
|
|
49
134
|
}
|
|
50
135
|
|
|
51
|
-
// result object
|
|
52
136
|
const result: GoogleTokenPayload = {
|
|
53
137
|
sub: payload.sub,
|
|
54
|
-
email: payload.email ||
|
|
138
|
+
email: payload.email || "",
|
|
55
139
|
email_verified: payload.email_verified || false,
|
|
56
140
|
name: payload.name,
|
|
57
141
|
picture: payload.picture
|
|
58
142
|
};
|
|
59
143
|
|
|
60
|
-
// remaining properties from payload
|
|
61
144
|
const { sub, email, email_verified, name, picture, ...rest } = payload;
|
|
62
145
|
Object.assign(result, rest);
|
|
63
146
|
|
|
64
147
|
return result;
|
|
65
148
|
|
|
66
149
|
} catch (err: any) {
|
|
67
|
-
logger.error("
|
|
68
|
-
|
|
69
|
-
|
|
150
|
+
logger.error("Google ID token verification failed", {
|
|
151
|
+
adapter: "google-auth",
|
|
152
|
+
operation: "verifyIdToken",
|
|
153
|
+
hasClientId: !!this.clientId,
|
|
154
|
+
reason: err?.message
|
|
70
155
|
});
|
|
71
|
-
|
|
72
|
-
if (err
|
|
156
|
+
|
|
157
|
+
if (err?.message?.includes("audience")) {
|
|
73
158
|
throw new AdapterError("Invalid Google client ID configured.");
|
|
74
159
|
}
|
|
75
|
-
|
|
76
|
-
throw new AdapterError(
|
|
160
|
+
|
|
161
|
+
throw new AdapterError("Google token verification failed.");
|
|
77
162
|
}
|
|
78
163
|
}
|
|
79
|
-
}
|
|
164
|
+
}
|
|
@@ -1,7 +1,102 @@
|
|
|
1
|
+
// import jwt from "jsonwebtoken";
|
|
2
|
+
// import { randomUUID } from "crypto";
|
|
3
|
+
// import { AdapterError } from "../core/errors/AdapterError.js";
|
|
4
|
+
// import { logError } from "../logging/index.js";
|
|
5
|
+
// import { logger } from "../logging";
|
|
6
|
+
|
|
7
|
+
// export interface JWTAdapterOptions {
|
|
8
|
+
// secret: string;
|
|
9
|
+
// expiresIn?: string | number;
|
|
10
|
+
// algorithm?: jwt.Algorithm;
|
|
11
|
+
// issuer?: string;
|
|
12
|
+
// audience?: string | string[];
|
|
13
|
+
// }
|
|
14
|
+
|
|
15
|
+
// export interface SignOptions {
|
|
16
|
+
// expiresIn?: string | number;
|
|
17
|
+
// jti?: string;
|
|
18
|
+
// subject?: string;
|
|
19
|
+
// issuer?: string;
|
|
20
|
+
// audience?: string | string[];
|
|
21
|
+
// }
|
|
22
|
+
|
|
23
|
+
// export class JWTAdapter {
|
|
24
|
+
// private secret: string;
|
|
25
|
+
// private expiresIn?: string | number;
|
|
26
|
+
// private algorithm: jwt.Algorithm;
|
|
27
|
+
// private issuer?: string;
|
|
28
|
+
// private audience?: string | string[];
|
|
29
|
+
|
|
30
|
+
// constructor(options: JWTAdapterOptions) {
|
|
31
|
+
// if (!options.secret) {
|
|
32
|
+
// throw new AdapterError("JWT secret is required");
|
|
33
|
+
// }
|
|
34
|
+
|
|
35
|
+
// if (options.secret.length < 32) {
|
|
36
|
+
// logger.warn("JWT secret shorter than 32 chars. Consider using stronger secret.");
|
|
37
|
+
// // logError("JWT secret is too short (minimum 32 characters recommended)");
|
|
38
|
+
// }
|
|
39
|
+
|
|
40
|
+
// this.secret = options.secret;
|
|
41
|
+
// this.expiresIn = options.expiresIn;
|
|
42
|
+
// this.algorithm = options.algorithm || 'HS256'; // Default algorithm
|
|
43
|
+
// this.issuer = options.issuer;
|
|
44
|
+
// this.audience = options.audience;
|
|
45
|
+
// }
|
|
46
|
+
|
|
47
|
+
// sign(payload: object, options?: SignOptions) {
|
|
48
|
+
// try {
|
|
49
|
+
// const jwtOptions: jwt.SignOptions = {
|
|
50
|
+
// algorithm: this.algorithm,
|
|
51
|
+
// issuer: options?.issuer || this.issuer,
|
|
52
|
+
// audience: options?.audience || this.audience,
|
|
53
|
+
// jwtid: options?.jti || randomUUID(),
|
|
54
|
+
// subject: options?.subject
|
|
55
|
+
// };
|
|
56
|
+
|
|
57
|
+
// if (options?.expiresIn !== undefined) {
|
|
58
|
+
// jwtOptions.expiresIn = options.expiresIn as number;
|
|
59
|
+
// } else if (this.expiresIn !== undefined) {
|
|
60
|
+
// jwtOptions.expiresIn = this.expiresIn as number;
|
|
61
|
+
// }
|
|
62
|
+
|
|
63
|
+
// return jwt.sign(payload, this.secret, jwtOptions);
|
|
64
|
+
|
|
65
|
+
// } catch (err: any) {
|
|
66
|
+
// logError("JWTAdapter.sign failed", { error: err?.message });
|
|
67
|
+
// throw new AdapterError(err?.message || "JWT sign failed");
|
|
68
|
+
// }
|
|
69
|
+
// }
|
|
70
|
+
|
|
71
|
+
// verify(token: string, options?: { audience?: string | string[] }) {
|
|
72
|
+
// try {
|
|
73
|
+
// const verifyOptions: jwt.VerifyOptions = {
|
|
74
|
+
// algorithms: [this.algorithm],
|
|
75
|
+
// issuer: this.issuer,
|
|
76
|
+
// audience: options?.audience as string || this.audience as string
|
|
77
|
+
// };
|
|
78
|
+
|
|
79
|
+
// return jwt.verify(token, this.secret, verifyOptions);
|
|
80
|
+
// } catch (err: any) {
|
|
81
|
+
// logError("JWTAdapter.verify failed", { error: err?.message });
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
// if (err.name === 'TokenExpiredError') {
|
|
85
|
+
// throw new AdapterError("JWT token has expired");
|
|
86
|
+
// }
|
|
87
|
+
// if (err.name === 'JsonWebTokenError') {
|
|
88
|
+
// throw new AdapterError("Invalid JWT token");
|
|
89
|
+
// }
|
|
90
|
+
|
|
91
|
+
// throw new AdapterError(err?.message || "JWT verification failed");
|
|
92
|
+
// }
|
|
93
|
+
// }
|
|
94
|
+
// }
|
|
95
|
+
|
|
96
|
+
|
|
1
97
|
import jwt from "jsonwebtoken";
|
|
2
|
-
import { randomUUID } from "crypto";
|
|
3
|
-
import { AdapterError } from "../core/errors/AdapterError
|
|
4
|
-
import { logError } from "../logging/index.js";
|
|
98
|
+
import { randomUUID } from "crypto";
|
|
99
|
+
import { AdapterError } from "../core/errors/AdapterError";
|
|
5
100
|
import { logger } from "../logging";
|
|
6
101
|
|
|
7
102
|
export interface JWTAdapterOptions {
|
|
@@ -14,7 +109,7 @@ export interface JWTAdapterOptions {
|
|
|
14
109
|
|
|
15
110
|
export interface SignOptions {
|
|
16
111
|
expiresIn?: string | number;
|
|
17
|
-
jti?: string;
|
|
112
|
+
jti?: string;
|
|
18
113
|
subject?: string;
|
|
19
114
|
issuer?: string;
|
|
20
115
|
audience?: string | string[];
|
|
@@ -33,13 +128,16 @@ export class JWTAdapter {
|
|
|
33
128
|
}
|
|
34
129
|
|
|
35
130
|
if (options.secret.length < 32) {
|
|
36
|
-
logger.warn("JWT secret
|
|
37
|
-
|
|
131
|
+
logger.warn("Weak JWT secret detected", {
|
|
132
|
+
adapter: "jwt",
|
|
133
|
+
operation: "init",
|
|
134
|
+
secretLength: options.secret.length
|
|
135
|
+
});
|
|
38
136
|
}
|
|
39
137
|
|
|
40
138
|
this.secret = options.secret;
|
|
41
139
|
this.expiresIn = options.expiresIn;
|
|
42
|
-
this.algorithm = options.algorithm ||
|
|
140
|
+
this.algorithm = options.algorithm || "HS256";
|
|
43
141
|
this.issuer = options.issuer;
|
|
44
142
|
this.audience = options.audience;
|
|
45
143
|
}
|
|
@@ -50,21 +148,26 @@ export class JWTAdapter {
|
|
|
50
148
|
algorithm: this.algorithm,
|
|
51
149
|
issuer: options?.issuer || this.issuer,
|
|
52
150
|
audience: options?.audience || this.audience,
|
|
53
|
-
jwtid: options?.jti || randomUUID(),
|
|
151
|
+
jwtid: options?.jti || randomUUID(),
|
|
54
152
|
subject: options?.subject
|
|
55
153
|
};
|
|
56
154
|
|
|
57
155
|
if (options?.expiresIn !== undefined) {
|
|
58
|
-
jwtOptions.expiresIn = options.expiresIn as
|
|
156
|
+
jwtOptions.expiresIn = options.expiresIn as any;
|
|
59
157
|
} else if (this.expiresIn !== undefined) {
|
|
60
|
-
jwtOptions.expiresIn = this.expiresIn as
|
|
158
|
+
jwtOptions.expiresIn = this.expiresIn as any;
|
|
61
159
|
}
|
|
62
160
|
|
|
63
161
|
return jwt.sign(payload, this.secret, jwtOptions);
|
|
64
162
|
|
|
65
163
|
} catch (err: any) {
|
|
66
|
-
|
|
67
|
-
|
|
164
|
+
logger.error("JWT signing failed", {
|
|
165
|
+
adapter: "jwt",
|
|
166
|
+
operation: "sign",
|
|
167
|
+
reason: err?.message
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
throw new AdapterError("JWT sign failed");
|
|
68
171
|
}
|
|
69
172
|
}
|
|
70
173
|
|
|
@@ -73,22 +176,27 @@ export class JWTAdapter {
|
|
|
73
176
|
const verifyOptions: jwt.VerifyOptions = {
|
|
74
177
|
algorithms: [this.algorithm],
|
|
75
178
|
issuer: this.issuer,
|
|
76
|
-
audience: options?.audience
|
|
179
|
+
audience: (options?.audience || this.audience) as string
|
|
77
180
|
};
|
|
78
181
|
|
|
79
182
|
return jwt.verify(token, this.secret, verifyOptions);
|
|
183
|
+
|
|
80
184
|
} catch (err: any) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
185
|
+
logger.error("JWT verification failed", {
|
|
186
|
+
adapter: "jwt",
|
|
187
|
+
operation: "verify",
|
|
188
|
+
reason: err?.message
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
if (err?.name === "TokenExpiredError") {
|
|
85
192
|
throw new AdapterError("JWT token has expired");
|
|
86
193
|
}
|
|
87
|
-
|
|
194
|
+
|
|
195
|
+
if (err?.name === "JsonWebTokenError") {
|
|
88
196
|
throw new AdapterError("Invalid JWT token");
|
|
89
197
|
}
|
|
90
|
-
|
|
91
|
-
throw new AdapterError(
|
|
198
|
+
|
|
199
|
+
throw new AdapterError("JWT verification failed");
|
|
92
200
|
}
|
|
93
201
|
}
|
|
94
|
-
}
|
|
202
|
+
}
|