beavuck-time 3.0.3 → 3.1.0
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 +264 -258
- package/build/config/corsOptions.js +19 -6
- package/build/middlewares/corsMiddleware.js +6 -4
- package/build/middlewares/errorHandler.js +8 -3
- package/build/server.js +11 -3
- package/build/utils/stringUtil.js +10 -0
- package/build/utils/urlUtil.js +3 -2
- package/openapi/swagger.json +1 -1
- package/package.json +11 -11
package/README.md
CHANGED
|
@@ -1,258 +1,264 @@
|
|
|
1
|
-
# ⏲️ Beavuck Time
|
|
2
|
-
|
|
3
|
-
[ ](https://www.npmjs.com/package/beavuck-time)
|
|
4
|
-
|
|
5
|
-
## 📊 Status
|
|
6
|
-
|
|
7
|
-
[](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
|
|
8
|
-
|
|
9
|
-
[](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
|
|
10
|
-
[](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
|
|
11
|
-
|
|
12
|
-
[](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
|
|
13
|
-
[](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
|
|
14
|
-
|
|
15
|
-
[](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
|
|
16
|
-
[](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
|
|
17
|
-
[](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
|
|
18
|
-
|
|
19
|
-
[](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
|
|
20
|
-
[](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
|
|
21
|
-
|
|
22
|
-
[](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
## 💡 Why
|
|
27
|
-
|
|
28
|
-
You can't count on client devices to all be set up with the correct date and time.
|
|
29
|
-
|
|
30
|
-
But keeping track of time is usually busywork, not the business of your core APIs. And you probably don't want to flood
|
|
31
|
-
your own APIs, whenever you want to get an accurate timestamp for entities in your apps.
|
|
32
|
-
|
|
33
|
-
So you can set up this simple microservice, whose only job should be to answer the question: "what time is it right
|
|
34
|
-
now?"
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
## 🎯 What
|
|
39
|
-
|
|
40
|
-
Get the current time in ISO format, in UTC timezone.
|
|
41
|
-
|
|
42
|
-
This lightweight service focuses on one job.
|
|
43
|
-
|
|
44
|
-
It needs no persistence layer, is capable of handling multiple concurrent requests, and is protected by a simple
|
|
45
|
-
CORS config for security and performance reasons.
|
|
46
|
-
|
|
47
|
-
Dockerized for easy deployment and scaling.
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
## 🔍 Where
|
|
52
|
-
|
|
53
|
-
The code lives on [GitLab](https://gitlab.com/beavuck-services/time),
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
### 🦊 GitLab
|
|
58
|
-
|
|
59
|
-
You can find the code on GitLab, where, once you have read
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
startServer
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
|
197
|
-
|
|
198
|
-
|
|
|
199
|
-
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
🖥 Core API
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
📱 Client
|
|
246
|
-
🕒 Time
|
|
247
|
-
|
|
248
|
-
🕒 Time
|
|
249
|
-
📱 Client
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
1
|
+
# ⏲️ Beavuck Time
|
|
2
|
+
|
|
3
|
+
[ ](https://www.npmjs.com/package/beavuck-time)
|
|
4
|
+
|
|
5
|
+
## 📊 Status
|
|
6
|
+
|
|
7
|
+
[](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
|
|
8
|
+
|
|
9
|
+
[](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
|
|
10
|
+
[](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
|
|
11
|
+
|
|
12
|
+
[](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
|
|
13
|
+
[](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
|
|
14
|
+
|
|
15
|
+
[](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
|
|
16
|
+
[](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
|
|
17
|
+
[](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
|
|
18
|
+
|
|
19
|
+
[](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
|
|
20
|
+
[](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
|
|
21
|
+
|
|
22
|
+
[](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 💡 Why
|
|
27
|
+
|
|
28
|
+
You can't count on client devices to all be set up with the correct date and time.
|
|
29
|
+
|
|
30
|
+
But keeping track of time is usually busywork, not the business of your core APIs. And you probably don't want to flood
|
|
31
|
+
your own APIs, whenever you want to get an accurate timestamp for entities in your apps.
|
|
32
|
+
|
|
33
|
+
So you can set up this simple microservice, whose only job should be to answer the question: "what time is it right
|
|
34
|
+
now?"
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 🎯 What
|
|
39
|
+
|
|
40
|
+
Get the current time in ISO format, in UTC timezone.
|
|
41
|
+
|
|
42
|
+
This lightweight service focuses on one job.
|
|
43
|
+
|
|
44
|
+
It needs no persistence layer, is capable of handling multiple concurrent requests, and is protected by a simple
|
|
45
|
+
CORS config for security and performance reasons.
|
|
46
|
+
|
|
47
|
+
Dockerized for easy deployment and scaling.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 🔍 Where
|
|
52
|
+
|
|
53
|
+
The code lives on [GitLab](https://gitlab.com/beavuck-services/time),
|
|
54
|
+
the Docker image is hosted on [Docker Hub](https://hub.docker.com/r/beavuck/time),
|
|
55
|
+
and it's also stored as a package on [NPM](https://www.npmjs.com/package/beavuck-time)
|
|
56
|
+
|
|
57
|
+
### 🦊 GitLab
|
|
58
|
+
|
|
59
|
+
You can find the code on GitLab, where, once you have read
|
|
60
|
+
the [CONTRIBUTING.md](https://gitlab.com/beavuck-services/time/-/blob/main/CONTRIBUTING.md?ref_type=heads) file, you can
|
|
61
|
+
also create issues and merge requests.
|
|
62
|
+
|
|
63
|
+
Feel free to fork the repo and make your own changes at will, as per
|
|
64
|
+
the [UNLICENSE](https://gitlab.com/beavuck-services/time/-/blob/main/UNLICENSE?ref_type=heads).
|
|
65
|
+
|
|
66
|
+
### 🐳 Docker Hub
|
|
67
|
+
|
|
68
|
+
Most devs will only use Docker Hub for their purposes with this project, to use it as is as a dependency for their own
|
|
69
|
+
projects. On Docker Hub, while you're developing, you should use the `beavuck/time:latest` tag to always get the latest
|
|
70
|
+
version.
|
|
71
|
+
|
|
72
|
+
When the time comes to go to production, to protect yourself from surprise breaking changes, you should instead point to
|
|
73
|
+
specific minor version tags, such as `beavuck/time:3.0` : those will not get breaking changes, but they will get
|
|
74
|
+
security upgrades and bug fixes while they're active.
|
|
75
|
+
|
|
76
|
+
### Ⓝ NPM
|
|
77
|
+
|
|
78
|
+
You can also use this service directly via npm:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npm install beavuck-time
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
#### Programmatically
|
|
85
|
+
|
|
86
|
+
If you want to run this service programmatically in your Node.js project:
|
|
87
|
+
|
|
88
|
+
```js
|
|
89
|
+
import { startServer } from 'beavuck-time'
|
|
90
|
+
|
|
91
|
+
startServer({
|
|
92
|
+
hostUrl: 'https://time-api.example.com',
|
|
93
|
+
trustedOrigins: 'https://my.app.com, https://my.app.com/*, https://my-other.app.com, https://my-other.app.com/*',
|
|
94
|
+
apiPort: 3000
|
|
95
|
+
})
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
You can rely on environment variables (BEAVUCK_TIME_HOST_URL, etc.) together with (or instead of) passing an options
|
|
99
|
+
object. Refer to the Docker Compose example below to see an exhaustive list of available environment variables and what
|
|
100
|
+
they do.
|
|
101
|
+
|
|
102
|
+
#### CLI
|
|
103
|
+
|
|
104
|
+
To run this service directly in your command line interface, just run:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
npx beavuck-time
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Refer to the Docker Compose example below to see an exhaustive list of available environment variables and what they do.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## ⚙️ Usage
|
|
115
|
+
|
|
116
|
+
### 🪧 Set up (docker-compose example)
|
|
117
|
+
|
|
118
|
+
(The section below supposes you're using a Docker image. If using the npm package, you can set those environment
|
|
119
|
+
variable
|
|
120
|
+
on the host directly, instead of doing so in the container like detailed below)
|
|
121
|
+
|
|
122
|
+
> **Note:** The Docker image does not include a `.env` file. All configuration must be passed via environment variables
|
|
123
|
+
> at runtime.
|
|
124
|
+
|
|
125
|
+
To run the service in a docker-compose environment, add this in your `docker-compose.yml`'s services section:
|
|
126
|
+
|
|
127
|
+
```yaml
|
|
128
|
+
time:
|
|
129
|
+
image: beavuck/time:latest
|
|
130
|
+
ports:
|
|
131
|
+
- 'SOME_PORT_NUMBER:3000'
|
|
132
|
+
# HOST_PORT:CONTAINER_PORT (Since we are in a container, CONTAINER_PORT corresponds to the API_PORT variable below)
|
|
133
|
+
environment:
|
|
134
|
+
- BEAVUCK_TIME_HOST_URL: https://time-api.example.com
|
|
135
|
+
# BEAVUCK_TIME_HOST_URL: That API's URL. Essential for CORS config.
|
|
136
|
+
- BEAVUCK_TIME_TRUSTED_ORIGINS: https://my.app.com,https://my-other.app.com
|
|
137
|
+
# 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
|
|
138
|
+
- BEAVUCK_TIME_API_PORT: 3000
|
|
139
|
+
# BEAVUCK_TIME_API_PORT: Optional. Internal port when in a container. Defaults to 3000
|
|
140
|
+
- BEAVUCK_TIME_RATE_LIMIT: -1
|
|
141
|
+
# 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
|
|
142
|
+
- BEAVUCK_TIME_LOG_LEVEL: info
|
|
143
|
+
# BEAVUCK_TIME_LOG_LEVEL: Optional. Logging levels include error, warn, info, http, verbose, debug, silly. Defaults to info
|
|
144
|
+
- BEAVUCK_TIME_MAX_LOG_FILES: 64
|
|
145
|
+
# BEAVUCK_TIME_MAX_LOG_FILES: Optional. Maximum number of logs to keep. This can be a number of files or number of days. If using days, add 'd' as the suffix. Default is 64
|
|
146
|
+
- BEAVUCK_TIME_MAX_SIZE_LOG_FILES: 1m
|
|
147
|
+
# BEAVUCK_TIME_MAX_SIZE_LOG_FILES: Optional. Maximum size of the file after which it will rotate. This can be a number of bytes, or units of kb, mb, and gb. If using the units, add 'k', 'm', or 'g' as the suffix. The units need to directly follow the number. Default is 1m
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Here's the simple docker compose file I used to test this service locally:
|
|
151
|
+
|
|
152
|
+
```yaml
|
|
153
|
+
services:
|
|
154
|
+
time:
|
|
155
|
+
image: beavuck/time:latest
|
|
156
|
+
ports:
|
|
157
|
+
- '3000:3000'
|
|
158
|
+
environment:
|
|
159
|
+
BEAVUCK_TIME_HOST_URL: http://127.0.0.1:3000
|
|
160
|
+
BEAVUCK_TIME_TRUSTED_ORIGINS: http://127.0.0.1:8000,http://localhost:8000
|
|
161
|
+
BEAVUCK_TIME_LOG_LEVEL: debug
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
When you're ready, just run your services with:
|
|
165
|
+
|
|
166
|
+
```shell
|
|
167
|
+
docker compose up -d
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### ✨ Using the service
|
|
171
|
+
|
|
172
|
+
Now, when you run:
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
curl --location 'http://localhost:{{SOME_PORT_NUMBER}}/now' \
|
|
176
|
+
--header 'Origin: {{SOME_TRUSTED_ORIGIN}}'
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
you should expect an answer such as:
|
|
180
|
+
|
|
181
|
+
```json
|
|
182
|
+
{
|
|
183
|
+
"now": "2024-06-15T12:35:48.022Z"
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## 🛡️ CORS
|
|
190
|
+
|
|
191
|
+
This service is protected by a CORS policy, which you can configure by setting the `TRUSTED_ORIGINS` environment
|
|
192
|
+
variable.
|
|
193
|
+
|
|
194
|
+
When you use the API, keep in mind what roles these headers play:
|
|
195
|
+
|
|
196
|
+
| `"Origin:"` | `"Referrer:"`* | Result |
|
|
197
|
+
|-----------------------------------------------------------|--------------------------------|--------|
|
|
198
|
+
| Defined and API `BEAVUCK_TIME_TRUSTED_ORIGINS` set to `*` | Whatever | ✅ |
|
|
199
|
+
| Trusted | Whatever | ✅ |
|
|
200
|
+
| Same as this API's host | Whatever | ✅ |
|
|
201
|
+
| Not defined | Same as this API's host | ✅ |
|
|
202
|
+
| Defined and not trusted | Whatever | 🛑 |
|
|
203
|
+
| Not defined | Not defined | 🛑 |
|
|
204
|
+
| Not defined or not trusted | Different from this API's host | 🛑 |
|
|
205
|
+
|
|
206
|
+
*AKA `Referer` (sic).
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## 📚 Use cases
|
|
211
|
+
|
|
212
|
+
### Batch `POST`s
|
|
213
|
+
|
|
214
|
+
Suppose you're creating timestamped entities in your app, and you want to send them in a batch to your API. Your API
|
|
215
|
+
knows the current time when it gets the request, but not the creation time of each entity.
|
|
216
|
+
|
|
217
|
+
```mermaid
|
|
218
|
+
sequenceDiagram
|
|
219
|
+
participant 📱 Client
|
|
220
|
+
participant 🖥 Core API
|
|
221
|
+
📱 Client ->> 🖥 Core API: POST /entities [{...}, {...}, {...}]
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
And querying your core API for the current time for each entity is a waste of resources -- that's why you're batching
|
|
225
|
+
the operation in the first place.
|
|
226
|
+
|
|
227
|
+
```mermaid
|
|
228
|
+
sequenceDiagram
|
|
229
|
+
participant 📱 Client
|
|
230
|
+
participant 🖥 Core API
|
|
231
|
+
📱 Client ->> 🖥 Core API: GET /now
|
|
232
|
+
🖥 Core API ->> 📱 Client: {"now": "2024-06-15T12:35:48.022Z"}
|
|
233
|
+
📱 Client ->> 🖥 Core API: GET /now
|
|
234
|
+
🖥 Core API ->> 📱 Client: {"now": "2024-06-15T12:35:49.071Z"}
|
|
235
|
+
📱 Client ->> 🖥 Core API: GET /now
|
|
236
|
+
🖥 Core API ->> 📱 Client: {"now": "2024-06-15T12:35:49.243Z"}
|
|
237
|
+
📱 Client ->> 🖥 Core API: POST /entities [{...}, {...}, {...}]
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
So you can use this service to get the current time whenever you need it, and use that as the creation time for each
|
|
241
|
+
entity.
|
|
242
|
+
|
|
243
|
+
```mermaid
|
|
244
|
+
sequenceDiagram
|
|
245
|
+
participant 📱 Client
|
|
246
|
+
participant 🕒 Time
|
|
247
|
+
participant 🖥 Core API
|
|
248
|
+
📱 Client ->> 🕒 Time: GET /now
|
|
249
|
+
🕒 Time ->> 📱 Client: {"now": "2024-06-15T12:35:48.022Z"}
|
|
250
|
+
📱 Client ->> 🕒 Time: GET /now
|
|
251
|
+
🕒 Time ->> 📱 Client: {"now": "2024-06-15T12:35:49.071Z"}
|
|
252
|
+
📱 Client ->> 🕒 Time: GET /now
|
|
253
|
+
🕒 Time ->> 📱 Client: {"now": "2024-06-15T12:35:49.243Z"}
|
|
254
|
+
📱 Client ->> 🖥 Core API: POST /entities [{...}, {...}, {...}]
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## 📜 License
|
|
260
|
+
|
|
261
|
+
Have at it.
|
|
262
|
+
|
|
263
|
+
This project uses the Unlicense. See
|
|
264
|
+
the [UNLICENSE](https://gitlab.com/beavuck-services/time/-/blob/main/UNLICENSE?ref_type=heads) file for details.
|
|
@@ -7,7 +7,7 @@ exports.initHostUrl = initHostUrl;
|
|
|
7
7
|
exports.getTrustedOrigins = getTrustedOrigins;
|
|
8
8
|
exports.initTrustedOrigins = initTrustedOrigins;
|
|
9
9
|
exports.isAllTrusted = isAllTrusted;
|
|
10
|
-
exports.
|
|
10
|
+
exports.isOriginTrusted = isOriginTrusted;
|
|
11
11
|
const corsError_1 = require("../errors/corsError");
|
|
12
12
|
const urlUtil_1 = require("../utils/urlUtil");
|
|
13
13
|
const http_status_codes_1 = require("http-status-codes");
|
|
@@ -15,7 +15,7 @@ exports.corsOptions = {
|
|
|
15
15
|
methods: ['GET', 'OPTIONS'],
|
|
16
16
|
optionsSuccessStatus: http_status_codes_1.StatusCodes.OK,
|
|
17
17
|
origin: (origin, callback) => {
|
|
18
|
-
if (isAllTrusted() || (0, urlUtil_1.isSameOrigin)(getHostUrl(), (0, urlUtil_1.tryParseUrl)(origin)) ||
|
|
18
|
+
if (!origin || isAllTrusted() || (0, urlUtil_1.isSameOrigin)(getHostUrl(), (0, urlUtil_1.tryParseUrl)(origin)) || isOriginTrusted(origin)) {
|
|
19
19
|
// eslint-disable-next-line no-restricted-syntax -- the http external library expects null
|
|
20
20
|
callback(null, true);
|
|
21
21
|
}
|
|
@@ -51,8 +51,21 @@ function initTrustedOrigins() {
|
|
|
51
51
|
function isAllTrusted() {
|
|
52
52
|
return getTrustedOrigins().includes('*');
|
|
53
53
|
}
|
|
54
|
-
function
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
function isOriginTrusted(origin) {
|
|
55
|
+
if (getTrustedOrigins().includes(origin)) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
return getTrustedOrigins().some(trusted => {
|
|
59
|
+
const trustedPattern = trusted.replace(/\/\*$/, '');
|
|
60
|
+
try {
|
|
61
|
+
const trustedUrl = new URL(trustedPattern);
|
|
62
|
+
const originUrl = new URL(origin);
|
|
63
|
+
return (trustedUrl.protocol === originUrl.protocol &&
|
|
64
|
+
trustedUrl.hostname === originUrl.hostname &&
|
|
65
|
+
trustedUrl.port === originUrl.port);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
58
71
|
}
|
|
@@ -11,12 +11,14 @@ const corsError_1 = require("../errors/corsError");
|
|
|
11
11
|
const logger_1 = require("../config/logger");
|
|
12
12
|
const beavuckTimeServerError_1 = require("../errors/beavuckTimeServerError");
|
|
13
13
|
const corsOptions_1 = require("../config/corsOptions");
|
|
14
|
+
const stringUtil_1 = require("../utils/stringUtil");
|
|
14
15
|
const corsMiddleware = (req, res, next) => {
|
|
15
16
|
const HOST_URL = (0, urlUtil_1.tryParseUrl)(process.env.BEAVUCK_TIME_HOST_URL ?? '');
|
|
17
|
+
const origin = req.headers.origin;
|
|
16
18
|
// 'referer' is a misspelling that was kept for compatibility: https://en.wikipedia.org/wiki/HTTP_referer
|
|
17
19
|
const referrerHeader = req.headers.referrer || req.headers.referer;
|
|
18
|
-
if (
|
|
19
|
-
logger_1.logger.debug(`CORS request from ${
|
|
20
|
+
if (origin) {
|
|
21
|
+
logger_1.logger.debug(`CORS request from ${(0, stringUtil_1.sanitizeForLog)(origin)}`);
|
|
20
22
|
(0, cors_1.default)(corsOptions_1.corsOptions)(req, res, next);
|
|
21
23
|
}
|
|
22
24
|
else if (!HOST_URL) {
|
|
@@ -26,8 +28,8 @@ const corsMiddleware = (req, res, next) => {
|
|
|
26
28
|
next();
|
|
27
29
|
}
|
|
28
30
|
else {
|
|
29
|
-
const
|
|
30
|
-
next(new corsError_1.CorsError(
|
|
31
|
+
const sanitizedReferrer = referrerHeader === undefined ? undefined : (0, stringUtil_1.sanitizeForLog)(referrerHeader);
|
|
32
|
+
next(new corsError_1.CorsError(`origin: ${origin}, referrer: ${sanitizedReferrer}`));
|
|
31
33
|
}
|
|
32
34
|
};
|
|
33
35
|
exports.corsMiddleware = corsMiddleware;
|
|
@@ -38,9 +38,14 @@ function handleBeavuckClientError(res, err) {
|
|
|
38
38
|
}
|
|
39
39
|
function handleBeavuckServerError(res, err) {
|
|
40
40
|
logger_1.logger.error(err);
|
|
41
|
-
|
|
41
|
+
res.status(err.code).json({ message: beavuckTimeServerError_1.BeavuckTimeServerError.baseMessage });
|
|
42
42
|
}
|
|
43
43
|
function handleOtherError(res, logMsg = `Unknown error`, resStatus = http_status_codes_1.StatusCodes.INTERNAL_SERVER_ERROR, resMsg = new errorResponse_1.ErrorResponse()) {
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
if (resStatus === http_status_codes_1.StatusCodes.INTERNAL_SERVER_ERROR) {
|
|
45
|
+
handleBeavuckServerError(res, new beavuckTimeServerError_1.BeavuckTimeServerError(logMsg));
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
logger_1.logger.error(`${logMsg}: ${JSON.stringify(resMsg)}`);
|
|
49
|
+
res.status(resStatus).json(resMsg);
|
|
50
|
+
}
|
|
46
51
|
}
|
package/build/server.js
CHANGED
|
@@ -22,10 +22,18 @@ function startServer(options = {}) {
|
|
|
22
22
|
| |_) | __/ (_| |\\ V /| |_| | (__| < ,==.," , , \\,===""
|
|
23
23
|
|_.__/ \\___|\\__,_| \\_/ \\__,_|\\___|_|\\_\\ < ,==) \\"'"=._.==) \\
|
|
24
24
|
\`=='' \`" \`"
|
|
25
|
+
888 d8b
|
|
26
|
+
888 Y8P
|
|
27
|
+
888
|
|
28
|
+
88888888888888b.d88b. .d88b.
|
|
29
|
+
888 888888 "888 "88bd8P Y8b
|
|
30
|
+
888 888888 888 88888888888
|
|
31
|
+
Y88b. 888888 888 888Y8b.
|
|
32
|
+
"Y888888888 888 888 "Y8888
|
|
33
|
+
|
|
34
|
+
Beavuck Time started successfully
|
|
25
35
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
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)
|
|
36
|
+
Ready on port ${process.env.BEAVUCK_TIME_API_PORT} (if this is running in a container, this port number is internal to the container)
|
|
29
37
|
`);
|
|
30
38
|
});
|
|
31
39
|
server.on('error', (err) => {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/utils/stringUtil.ts
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.sanitizeForLog = sanitizeForLog;
|
|
5
|
+
function sanitizeForLog(value) {
|
|
6
|
+
// About the warning suppression: we're specifically working on control characters here,
|
|
7
|
+
// to sanitize strings for protection against log injection
|
|
8
|
+
// eslint-disable-next-line no-control-regex
|
|
9
|
+
return value.replaceAll(/[\u0000-\u001F\u007F-\u009F]/g, '');
|
|
10
|
+
}
|
package/build/utils/urlUtil.js
CHANGED
|
@@ -5,8 +5,9 @@ exports.isSameOrigin = isSameOrigin;
|
|
|
5
5
|
exports.tryParseUrl = tryParseUrl;
|
|
6
6
|
const node_url_1 = require("node:url");
|
|
7
7
|
const logger_1 = require("../config/logger");
|
|
8
|
+
const stringUtil_1 = require("./stringUtil");
|
|
8
9
|
function isSameOrigin(thisUrl, thatUrl) {
|
|
9
|
-
logger_1.logger.debug(`Comparing origins: ${thisUrl?.origin} and ${thatUrl?.origin}`);
|
|
10
|
+
logger_1.logger.debug(`Comparing origins: ${thisUrl?.origin ? (0, stringUtil_1.sanitizeForLog)(thisUrl?.origin) : undefined} and ${thatUrl?.origin ? (0, stringUtil_1.sanitizeForLog)(thatUrl?.origin) : undefined}`);
|
|
10
11
|
if (!thisUrl || !thatUrl) {
|
|
11
12
|
return false;
|
|
12
13
|
}
|
|
@@ -27,7 +28,7 @@ function tryParseUrl(urlString) {
|
|
|
27
28
|
return new node_url_1.URL(urlString);
|
|
28
29
|
}
|
|
29
30
|
catch (error) {
|
|
30
|
-
logger_1.logger.warn(`Invalid URL string: ${urlString} (${error})`);
|
|
31
|
+
logger_1.logger.warn(`Invalid URL string: ${(0, stringUtil_1.sanitizeForLog)(urlString)} (${error})`);
|
|
31
32
|
}
|
|
32
33
|
return undefined;
|
|
33
34
|
}
|
package/openapi/swagger.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "beavuck-time",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Get time in ISO format, in UTC timezone, from a simple, lightweight node server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"time",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"beavuck-time": "build/server.js"
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
|
-
"run-integration-tests": "cd
|
|
36
|
+
"run-integration-tests": "cd api_tests/time_api_tests && npx --yes @usebruno/cli run --env local --bail",
|
|
37
37
|
"up-patch": "npm version patch --no-git-tag-version",
|
|
38
38
|
"up-minor": "npm version minor --no-git-tag-version",
|
|
39
39
|
"up-major": "npm version major --no-git-tag-version",
|
|
@@ -52,9 +52,9 @@
|
|
|
52
52
|
"@tsoa/runtime": "^6.6.0",
|
|
53
53
|
"cors": "^2.8.6",
|
|
54
54
|
"express": "^5.2.1",
|
|
55
|
-
"express-rate-limit": "^8.3.
|
|
55
|
+
"express-rate-limit": "^8.3.1",
|
|
56
56
|
"http-status-codes": "^2.3.0",
|
|
57
|
-
"joi": "^18.
|
|
57
|
+
"joi": "^18.1.1",
|
|
58
58
|
"tsoa": "^6.6.0",
|
|
59
59
|
"winston": "^3.19.0",
|
|
60
60
|
"winston-daily-rotate-file": "^5.0.0"
|
|
@@ -64,19 +64,19 @@
|
|
|
64
64
|
"@types/cors": "^2.8.19",
|
|
65
65
|
"@types/express": "^5.0.6",
|
|
66
66
|
"@types/jest": "^30.0.0",
|
|
67
|
-
"@types/node": "^25.
|
|
67
|
+
"@types/node": "^25.5.0",
|
|
68
68
|
"@types/supertest": "^7.2.0",
|
|
69
|
-
"eslint": "^10.0
|
|
69
|
+
"eslint": "^10.1.0",
|
|
70
70
|
"globals": "^17.4.0",
|
|
71
|
-
"jest": "^30.
|
|
72
|
-
"jest-util": "^30.
|
|
73
|
-
"knip": "^
|
|
74
|
-
"npm-check-updates": "^19.6.
|
|
71
|
+
"jest": "^30.3.0",
|
|
72
|
+
"jest-util": "^30.3.0",
|
|
73
|
+
"knip": "^6.1.0",
|
|
74
|
+
"npm-check-updates": "^19.6.6",
|
|
75
75
|
"prettier": "3.8.1",
|
|
76
76
|
"supertest": "^7.2.2",
|
|
77
77
|
"ts-jest": "^29.4.6",
|
|
78
78
|
"typescript": "^5.9.3",
|
|
79
|
-
"typescript-eslint": "^8.
|
|
79
|
+
"typescript-eslint": "^8.57.2"
|
|
80
80
|
},
|
|
81
81
|
"engines": {
|
|
82
82
|
"node": ">=20.12.0"
|