beavuck-time 2.3.13 → 2.4.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/README.md +12 -5
- package/build/config/corsOptions.js +9 -4
- package/build/errors/base/{BeavuckTimeError.js → beavuckTimeError.js} +1 -1
- package/build/errors/{BeavuckTimeClientError.js → beavuckTimeClientError.js} +3 -3
- package/build/errors/{BeavuckTimeServerError.js → beavuckTimeServerError.js} +3 -3
- package/build/errors/{CorsError.js → corsError.js} +3 -3
- package/build/errors/errorResponse.js +14 -0
- package/build/middlewares/corsMiddleware.js +4 -4
- package/build/middlewares/errorHandler.js +7 -8
- package/build/middlewares/rateLimiter.js +1 -1
- package/build/server.js +38 -17
- package/build/utils/urlUtil.js +6 -4
- package/openapi/swagger.json +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -86,12 +86,19 @@ If you want to run this service programmatically in your Node.js project:
|
|
|
86
86
|
```js
|
|
87
87
|
import { startServer } from 'beavuck-time'
|
|
88
88
|
|
|
89
|
-
startServer(
|
|
89
|
+
startServer({
|
|
90
|
+
hostUrl: 'https://time-api.example.com',
|
|
91
|
+
trustedOrigins: 'https://my.app.com, https://my.app.com/*, https://my-other.app.com, https://my-other.app.com/*',
|
|
92
|
+
apiPort: 3000,
|
|
93
|
+
rateLimit: -1,
|
|
94
|
+
logLevel: 'info',
|
|
95
|
+
maxLogFiles: 64,
|
|
96
|
+
maxSizeLogFiles: '1m',
|
|
97
|
+
})
|
|
90
98
|
```
|
|
91
99
|
|
|
92
|
-
You can rely on environment variables (BEAVUCK_TIME_HOST_URL, etc.)
|
|
93
|
-
|
|
94
|
-
Refer to the Docker Compose example below to see an exhaustive list of available environment variables and what they do.
|
|
100
|
+
You can rely on environment variables (BEAVUCK_TIME_HOST_URL, etc.) instead of passing an options object. Refer to
|
|
101
|
+
the Docker Compose example below to see an exhaustive list of available environment variables and what they do.
|
|
95
102
|
|
|
96
103
|
#### CLI
|
|
97
104
|
|
|
@@ -127,7 +134,7 @@ time:
|
|
|
127
134
|
# BEAVUCK_TIME_TRUSTED_ORIGINS: To allow requests from any origin, include * (not recommended). If empty, will only allow requests from the HOST_URL's origin. Defaults to the HOST_URL's origin
|
|
128
135
|
- BEAVUCK_TIME_API_PORT: 3000
|
|
129
136
|
# BEAVUCK_TIME_API_PORT: Optional. Internal port when in a container. Defaults to 3000
|
|
130
|
-
- BEAVUCK_TIME_RATE_LIMIT:
|
|
137
|
+
- BEAVUCK_TIME_RATE_LIMIT: -1
|
|
131
138
|
# BEAVUCK_TIME_RATE_LIMIT: Optional. Max allowed number of requests per minute for each IP address. If negative or 0, no limit. Defaults to no limit
|
|
132
139
|
- BEAVUCK_TIME_LOG_LEVEL: info
|
|
133
140
|
# BEAVUCK_TIME_LOG_LEVEL: Optional. Logging levels include error, warn, info, http, verbose, debug, silly. Defaults to info
|
|
@@ -11,7 +11,7 @@ exports.getTrustedOrigins = getTrustedOrigins;
|
|
|
11
11
|
exports.initTrustedOrigins = initTrustedOrigins;
|
|
12
12
|
exports.isAllTrusted = isAllTrusted;
|
|
13
13
|
exports.isOriginAbsentOrTrusted = isOriginAbsentOrTrusted;
|
|
14
|
-
const
|
|
14
|
+
const corsError_1 = require("../errors/corsError");
|
|
15
15
|
const urlUtil_1 = require("../utils/urlUtil");
|
|
16
16
|
const http_status_codes_1 = require("http-status-codes");
|
|
17
17
|
const dotenvx_1 = __importDefault(require("@dotenvx/dotenvx"));
|
|
@@ -27,7 +27,7 @@ exports.corsOptions = {
|
|
|
27
27
|
callback(null, true);
|
|
28
28
|
}
|
|
29
29
|
else {
|
|
30
|
-
callback(new
|
|
30
|
+
callback(new corsError_1.CorsError(origin), false);
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
33
|
};
|
|
@@ -50,11 +50,16 @@ function getTrustedOrigins() {
|
|
|
50
50
|
}
|
|
51
51
|
function initTrustedOrigins() {
|
|
52
52
|
const trustedOrigins = process.env.BEAVUCK_TIME_TRUSTED_ORIGINS;
|
|
53
|
-
TRUSTED_ORIGINS = trustedOrigins
|
|
53
|
+
TRUSTED_ORIGINS = trustedOrigins
|
|
54
|
+
.split(',')
|
|
55
|
+
.map(to => to.trim())
|
|
56
|
+
.filter(to => to.length > 0);
|
|
54
57
|
}
|
|
55
58
|
function isAllTrusted() {
|
|
56
59
|
return getTrustedOrigins().includes('*');
|
|
57
60
|
}
|
|
58
61
|
function isOriginAbsentOrTrusted(origin) {
|
|
59
|
-
return !origin ||
|
|
62
|
+
return (!origin ||
|
|
63
|
+
getTrustedOrigins().includes(origin) ||
|
|
64
|
+
getTrustedOrigins().some(to => to.includes('/*') && origin.startsWith(to)));
|
|
60
65
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// src/errors/
|
|
2
|
+
// src/errors/beavuckTimeClientError.ts
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.BeavuckTimeClientError = void 0;
|
|
5
|
-
const
|
|
5
|
+
const beavuckTimeError_1 = require("./base/beavuckTimeError");
|
|
6
6
|
const http_status_codes_1 = require("http-status-codes");
|
|
7
|
-
class BeavuckTimeClientError extends
|
|
7
|
+
class BeavuckTimeClientError extends beavuckTimeError_1.BeavuckTimeError {
|
|
8
8
|
static baseMessage = 'Client Error';
|
|
9
9
|
constructor(message, code = http_status_codes_1.StatusCodes.BAD_REQUEST) {
|
|
10
10
|
super(`${BeavuckTimeClientError.baseMessage}: ${message}`, code);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// src/errors/
|
|
2
|
+
// src/errors/beavuckTimeServerError.ts
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.BeavuckTimeServerError = void 0;
|
|
5
|
-
const
|
|
5
|
+
const beavuckTimeError_1 = require("./base/beavuckTimeError");
|
|
6
6
|
const http_status_codes_1 = require("http-status-codes");
|
|
7
|
-
class BeavuckTimeServerError extends
|
|
7
|
+
class BeavuckTimeServerError extends beavuckTimeError_1.BeavuckTimeError {
|
|
8
8
|
static baseMessage = 'Internal Server Error';
|
|
9
9
|
constructor(message, code = http_status_codes_1.StatusCodes.INTERNAL_SERVER_ERROR) {
|
|
10
10
|
super(`${BeavuckTimeServerError.baseMessage}: ${message}`, code);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// src/errors/
|
|
2
|
+
// src/errors/corsError.ts
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.CorsError = void 0;
|
|
5
5
|
const http_status_codes_1 = require("http-status-codes");
|
|
6
|
-
const
|
|
7
|
-
class CorsError extends
|
|
6
|
+
const beavuckTimeClientError_1 = require("./beavuckTimeClientError");
|
|
7
|
+
class CorsError extends beavuckTimeClientError_1.BeavuckTimeClientError {
|
|
8
8
|
static baseMessage = 'Origin forbidden by CORS';
|
|
9
9
|
constructor(origin) {
|
|
10
10
|
super(`${CorsError.baseMessage}: ${origin}`, http_status_codes_1.StatusCodes.FORBIDDEN);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/errors/errorResponse.ts
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.ErrorResponse = void 0;
|
|
5
|
+
const beavuckTimeServerError_1 = require("./beavuckTimeServerError");
|
|
6
|
+
class ErrorResponse {
|
|
7
|
+
message;
|
|
8
|
+
details;
|
|
9
|
+
constructor(message = beavuckTimeServerError_1.BeavuckTimeServerError.baseMessage, details) {
|
|
10
|
+
this.message = message;
|
|
11
|
+
this.details = details;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.ErrorResponse = ErrorResponse;
|
|
@@ -7,9 +7,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
7
7
|
exports.corsMiddleware = void 0;
|
|
8
8
|
const cors_1 = __importDefault(require("cors"));
|
|
9
9
|
const urlUtil_1 = require("../utils/urlUtil");
|
|
10
|
-
const
|
|
10
|
+
const corsError_1 = require("../errors/corsError");
|
|
11
11
|
const logger_1 = require("../config/logger");
|
|
12
|
-
const
|
|
12
|
+
const beavuckTimeServerError_1 = require("../errors/beavuckTimeServerError");
|
|
13
13
|
const corsOptions_1 = require("../config/corsOptions");
|
|
14
14
|
const corsMiddleware = (req, res, next) => {
|
|
15
15
|
const HOST_URL = (0, urlUtil_1.tryParseUrl)(process.env.BEAVUCK_TIME_HOST_URL ?? '');
|
|
@@ -21,14 +21,14 @@ const corsMiddleware = (req, res, next) => {
|
|
|
21
21
|
}
|
|
22
22
|
else if (!HOST_URL) {
|
|
23
23
|
const noHostUrl = 'Host URL not set';
|
|
24
|
-
next(new
|
|
24
|
+
next(new beavuckTimeServerError_1.BeavuckTimeServerError(noHostUrl));
|
|
25
25
|
}
|
|
26
26
|
else if ((0, urlUtil_1.isSameOrigin)(HOST_URL, (0, urlUtil_1.tryParseUrl)(referrerHeader ?? ''))) {
|
|
27
27
|
next();
|
|
28
28
|
}
|
|
29
29
|
else {
|
|
30
30
|
const badOriginAndOrReferrer = `origin: ${req.headers.origin}, referrer: ${referrerHeader}`;
|
|
31
|
-
next(new
|
|
31
|
+
next(new corsError_1.CorsError(badOriginAndOrReferrer));
|
|
32
32
|
}
|
|
33
33
|
};
|
|
34
34
|
exports.corsMiddleware = corsMiddleware;
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.errorHandler = void 0;
|
|
5
5
|
const logger_1 = require("../config/logger");
|
|
6
|
-
const
|
|
7
|
-
const
|
|
6
|
+
const beavuckTimeClientError_1 = require("../errors/beavuckTimeClientError");
|
|
7
|
+
const beavuckTimeServerError_1 = require("../errors/beavuckTimeServerError");
|
|
8
8
|
const http_status_codes_1 = require("http-status-codes");
|
|
9
9
|
const tsoa_1 = require("tsoa");
|
|
10
|
+
const errorResponse_1 = require("../errors/errorResponse");
|
|
10
11
|
const errorHandler = (err, req, res, next) => {
|
|
11
12
|
if (err instanceof tsoa_1.ValidateError) {
|
|
12
13
|
const logMsg = `Validation error on ${req.path}: ${err.fields}`;
|
|
@@ -14,11 +15,11 @@ const errorHandler = (err, req, res, next) => {
|
|
|
14
15
|
const resMsgObj = { message: 'Validation Error', details: err.fields };
|
|
15
16
|
handleOtherError(res, logMsg, resStatus, resMsgObj);
|
|
16
17
|
}
|
|
17
|
-
else if (err instanceof
|
|
18
|
+
else if (err instanceof beavuckTimeClientError_1.BeavuckTimeClientError) {
|
|
18
19
|
handleBeavuckClientError(res, err);
|
|
19
20
|
}
|
|
20
|
-
else if (err instanceof
|
|
21
|
-
handleBeavuckServerError(res,
|
|
21
|
+
else if (err instanceof beavuckTimeServerError_1.BeavuckTimeServerError || err instanceof Error) {
|
|
22
|
+
handleBeavuckServerError(res, beavuckTimeServerError_1.BeavuckTimeServerError.fromError(err));
|
|
22
23
|
}
|
|
23
24
|
else if (err) {
|
|
24
25
|
handleOtherError(res);
|
|
@@ -39,9 +40,7 @@ function handleBeavuckServerError(res, err) {
|
|
|
39
40
|
logger_1.logger.error(err);
|
|
40
41
|
sendErrorResponse(res, err);
|
|
41
42
|
}
|
|
42
|
-
function handleOtherError(res, logMsg = `Unknown error`, resStatus = http_status_codes_1.StatusCodes.INTERNAL_SERVER_ERROR, resMsg = {
|
|
43
|
-
message: BeavuckTimeServerError_1.BeavuckTimeServerError.baseMessage,
|
|
44
|
-
}) {
|
|
43
|
+
function handleOtherError(res, logMsg = `Unknown error`, resStatus = http_status_codes_1.StatusCodes.INTERNAL_SERVER_ERROR, resMsg = new errorResponse_1.ErrorResponse()) {
|
|
45
44
|
logger_1.logger.error(`${logMsg}: ${JSON.stringify(resMsg)}`);
|
|
46
45
|
res.status(resStatus).json(resMsg);
|
|
47
46
|
}
|
|
@@ -7,7 +7,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
7
7
|
exports.rateLimiter = void 0;
|
|
8
8
|
const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
|
|
9
9
|
const NO_RATE_LIMIT = '-1';
|
|
10
|
-
const RATE_LIMIT = parseInt(process.env.BEAVUCK_TIME_RATE_LIMIT ?? NO_RATE_LIMIT, 10);
|
|
10
|
+
const RATE_LIMIT = Number.parseInt(process.env.BEAVUCK_TIME_RATE_LIMIT ?? NO_RATE_LIMIT, 10);
|
|
11
11
|
const SECONDS_IN_ONE_MINUTE = 60;
|
|
12
12
|
const MILLISECONDS_IN_ONE_SECOND = 1000;
|
|
13
13
|
const ONE_MINUTE = SECONDS_IN_ONE_MINUTE * MILLISECONDS_IN_ONE_SECOND;
|
package/build/server.js
CHANGED
|
@@ -10,26 +10,14 @@ const api_1 = require("./api");
|
|
|
10
10
|
const logger_1 = require("./config/logger");
|
|
11
11
|
const urlUtil_1 = require("./utils/urlUtil");
|
|
12
12
|
const dotenvx_1 = __importDefault(require("@dotenvx/dotenvx"));
|
|
13
|
+
// Load environment variables from .env file, if available
|
|
14
|
+
// but don't error if the file is missing
|
|
13
15
|
dotenvx_1.default.config({
|
|
14
16
|
ignore: ['MISSING_ENV_FILE'],
|
|
15
17
|
});
|
|
16
|
-
function validateEnv({ hostUrl, trustedOrigins, }) {
|
|
17
|
-
if (!hostUrl || !(0, urlUtil_1.tryParseUrl)(hostUrl)) {
|
|
18
|
-
logger_1.logger.error('HOST_URL not set or is not a valid URL');
|
|
19
|
-
process.exit(1);
|
|
20
|
-
}
|
|
21
|
-
if (!trustedOrigins) {
|
|
22
|
-
logger_1.logger.error('TRUSTED_ORIGINS not set');
|
|
23
|
-
process.exit(1);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
// FIXME the server options method causes an exception, npm users will have to use a .env file for now
|
|
27
18
|
function startServer(options = {}) {
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
const TRUSTED_ORIGINS = options.trustedOrigins ?? process.env.BEAVUCK_TIME_TRUSTED_ORIGINS ?? '';
|
|
31
|
-
validateEnv({ hostUrl: HOST_URL, trustedOrigins: TRUSTED_ORIGINS });
|
|
32
|
-
const server = api_1.api.listen(API_PORT, () => {
|
|
19
|
+
manageEnvVars(options);
|
|
20
|
+
const server = api_1.api.listen(process.env.BEAVUCK_TIME_API_PORT, () => {
|
|
33
21
|
logger_1.logger.info(`
|
|
34
22
|
| | | | ,=.
|
|
35
23
|
| |__ ___ __ ___ ___ _ ___| | __ ,=""""==.__.=" o".___
|
|
@@ -40,7 +28,7 @@ function startServer(options = {}) {
|
|
|
40
28
|
|
|
41
29
|
Beavuck Time microservice started successfully
|
|
42
30
|
|
|
43
|
-
Ready on API port ${
|
|
31
|
+
Ready on API port ${process.env.BEAVUCK_TIME_API_PORT} (if this is running in a container, this port number is internal to the container)
|
|
44
32
|
`);
|
|
45
33
|
});
|
|
46
34
|
server.on('error', (err) => {
|
|
@@ -60,6 +48,39 @@ function startServer(options = {}) {
|
|
|
60
48
|
process.on(SIGINT, () => gracefulShutdown(SIGINT));
|
|
61
49
|
return server;
|
|
62
50
|
}
|
|
51
|
+
function manageEnvVars(options) {
|
|
52
|
+
process.env.BEAVUCK_TIME_API_PORT =
|
|
53
|
+
options.apiPort === undefined
|
|
54
|
+
? '3000'
|
|
55
|
+
: String(options.apiPort);
|
|
56
|
+
process.env.BEAVUCK_TIME_HOST_URL =
|
|
57
|
+
options.hostUrl ?? process.env.BEAVUCK_TIME_HOST_URL;
|
|
58
|
+
process.env.BEAVUCK_TIME_TRUSTED_ORIGINS =
|
|
59
|
+
options.trustedOrigins ?? process.env.BEAVUCK_TIME_TRUSTED_ORIGINS;
|
|
60
|
+
process.env.BEAVUCK_TIME_RATE_LIMIT =
|
|
61
|
+
options.rateLimit === undefined
|
|
62
|
+
? process.env.BEAVUCK_TIME_RATE_LIMIT
|
|
63
|
+
: String(options.rateLimit);
|
|
64
|
+
process.env.BEAVUCK_TIME_LOG_LEVEL =
|
|
65
|
+
options.logLevel ?? process.env.BEAVUCK_TIME_LOG_LEVEL;
|
|
66
|
+
process.env.BEAVUCK_TIME_MAX_LOG_FILES =
|
|
67
|
+
options.maxLogFiles === undefined
|
|
68
|
+
? process.env.BEAVUCK_TIME_MAX_LOG_FILES
|
|
69
|
+
: String(options.maxLogFiles);
|
|
70
|
+
process.env.BEAVUCK_TIME_MAX_SIZE_LOG_FILES =
|
|
71
|
+
options.maxSizeLogFiles ?? process.env.BEAVUCK_TIME_MAX_SIZE_LOG_FILES;
|
|
72
|
+
validateEnv();
|
|
73
|
+
}
|
|
74
|
+
function validateEnv() {
|
|
75
|
+
if (!process.env.BEAVUCK_TIME_HOST_URL || !(0, urlUtil_1.tryParseUrl)(process.env.BEAVUCK_TIME_HOST_URL)) {
|
|
76
|
+
logger_1.logger.error('HOST_URL not set or is not a valid URL');
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
if (!process.env.BEAVUCK_TIME_TRUSTED_ORIGINS) {
|
|
80
|
+
logger_1.logger.error('TRUSTED_ORIGINS not set');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
63
84
|
// Support running as standalone binary
|
|
64
85
|
if (require.main === module) {
|
|
65
86
|
startServer();
|
package/build/utils/urlUtil.js
CHANGED
|
@@ -3,14 +3,16 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.isSameOrigin = isSameOrigin;
|
|
5
5
|
exports.tryParseUrl = tryParseUrl;
|
|
6
|
-
const
|
|
6
|
+
const node_url_1 = require("node:url");
|
|
7
7
|
const logger_1 = require("../config/logger");
|
|
8
8
|
function isSameOrigin(thisUrl, thatUrl) {
|
|
9
9
|
logger_1.logger.debug(`Comparing origins: ${thisUrl?.origin} and ${thatUrl?.origin}`);
|
|
10
|
-
if (!thisUrl || !thatUrl)
|
|
10
|
+
if (!thisUrl || !thatUrl) {
|
|
11
11
|
return false;
|
|
12
|
-
|
|
12
|
+
}
|
|
13
|
+
if (thisUrl.origin === thatUrl.origin) {
|
|
13
14
|
return true;
|
|
15
|
+
}
|
|
14
16
|
const isSameProtocol = thisUrl.protocol === thatUrl.protocol;
|
|
15
17
|
const isSameHost = thisUrl.hostname === thatUrl.hostname;
|
|
16
18
|
const isSamePort = thisUrl.port === thatUrl.port;
|
|
@@ -22,7 +24,7 @@ function tryParseUrl(urlString) {
|
|
|
22
24
|
return undefined;
|
|
23
25
|
}
|
|
24
26
|
try {
|
|
25
|
-
return new
|
|
27
|
+
return new node_url_1.URL(urlString);
|
|
26
28
|
}
|
|
27
29
|
catch (error) {
|
|
28
30
|
logger_1.logger.warn(`Invalid URL string: ${urlString} (${error})`);
|
package/openapi/swagger.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "beavuck-time",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.1",
|
|
4
4
|
"description": "Get time in ISO format, in UTC timezone, from a simple, lightweight node server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"time",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"test": "DOTENV_CONFIG_PATH=./.env.test jest --coverage",
|
|
47
47
|
"build": "tsoa spec-and-routes && tsc",
|
|
48
48
|
"start": "node build/server.js",
|
|
49
|
-
"go": "rm -rf build && npm install && npm run build && npm run clean && npm run test && npm start"
|
|
49
|
+
"go": "rm -rf build && npm install && npm run build && npm run clean && npm run test && npm run start"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@dotenvx/dotenvx": "^1.51.0",
|