@ditojs/server 2.90.0 → 2.91.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ditojs/server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.91.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Dito.js Server – Dito.js is a declarative and modern web framework, based on Objection.js, Koa.js and Vue.js",
|
|
6
6
|
"repository": "https://github.com/ditojs/dito/tree/main/packages/server",
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
"node >= 18"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@ditojs/admin": "^2.
|
|
29
|
-
"@ditojs/build": "^2.
|
|
30
|
-
"@ditojs/router": "^2.
|
|
31
|
-
"@ditojs/utils": "^2.
|
|
28
|
+
"@ditojs/admin": "^2.91.0",
|
|
29
|
+
"@ditojs/build": "^2.91.0",
|
|
30
|
+
"@ditojs/router": "^2.91.0",
|
|
31
|
+
"@ditojs/utils": "^2.91.0",
|
|
32
32
|
"@koa/cors": "^5.0.0",
|
|
33
33
|
"@koa/etag": "^5.0.2",
|
|
34
34
|
"@koa/multer": "^4.0.0",
|
|
@@ -90,5 +90,5 @@
|
|
|
90
90
|
"objection": "^3.1.5",
|
|
91
91
|
"typescript": "^5.9.3"
|
|
92
92
|
},
|
|
93
|
-
"gitHead": "
|
|
93
|
+
"gitHead": "cc64039676a475376ae68b11ad1760dde5fe789c"
|
|
94
94
|
}
|
package/src/app/Application.js
CHANGED
|
@@ -1007,9 +1007,6 @@ export class Application extends Koa {
|
|
|
1007
1007
|
}
|
|
1008
1008
|
}
|
|
1009
1009
|
const importedFile = await storage.addFile(file, data)
|
|
1010
|
-
// Sign the imported foreign file so it passes verification when
|
|
1011
|
-
// createAssets() triggers $parseJson() → convertAssetFile().
|
|
1012
|
-
storage.signAssetFile(importedFile)
|
|
1013
1010
|
await this.createAssets(storage, [importedFile], 0, transaction)
|
|
1014
1011
|
importedFiles.push(importedFile)
|
|
1015
1012
|
// Merge back the changed file properties into the actual file
|
package/src/mixins/AssetMixin.js
CHANGED
|
@@ -66,13 +66,11 @@ export const AssetMixin = mixin(
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
// @override
|
|
69
|
-
$parseJson(json
|
|
69
|
+
$parseJson(json) {
|
|
70
70
|
const { file, storage } = json
|
|
71
71
|
// Convert `AssetMixin#file` to an `AssetFile` instance:
|
|
72
72
|
if (file && storage) {
|
|
73
|
-
this.constructor.app
|
|
74
|
-
.getStorage(storage)
|
|
75
|
-
?.convertAssetFile(file, { trusted })
|
|
73
|
+
this.constructor.app.getStorage(storage)?.convertAssetFile(file)
|
|
76
74
|
}
|
|
77
75
|
return json
|
|
78
76
|
}
|
package/src/models/Model.js
CHANGED
|
@@ -609,11 +609,11 @@ export class Model extends objection.Model {
|
|
|
609
609
|
}
|
|
610
610
|
// Also run through normal $parseJson(), for handling of `Date` and
|
|
611
611
|
// `AssetFile`.
|
|
612
|
-
return this.$parseJson(json
|
|
612
|
+
return this.$parseJson(json)
|
|
613
613
|
}
|
|
614
614
|
|
|
615
615
|
// @override
|
|
616
|
-
$parseJson(json
|
|
616
|
+
$parseJson(json) {
|
|
617
617
|
const { constructor } = this
|
|
618
618
|
for (const key of constructor.dateAttributes) {
|
|
619
619
|
const date = json[key]
|
|
@@ -634,7 +634,7 @@ export class Model extends objection.Model {
|
|
|
634
634
|
if (isArray(data)) {
|
|
635
635
|
data.forEach(convertToAssetFiles)
|
|
636
636
|
} else {
|
|
637
|
-
storage.convertAssetFile(data
|
|
637
|
+
storage.convertAssetFile(data)
|
|
638
638
|
}
|
|
639
639
|
}
|
|
640
640
|
}
|
package/src/storage/Storage.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
2
|
import { URL } from 'url'
|
|
3
|
-
import crypto from 'crypto'
|
|
4
3
|
import multer from '@koa/multer'
|
|
5
4
|
import picomatch from 'picomatch'
|
|
6
5
|
import { PassThrough } from 'stream'
|
|
7
6
|
import { readMediaAttributes } from 'leather'
|
|
8
7
|
import { hyphenate, toPromiseCallback } from '@ditojs/utils'
|
|
9
8
|
import { AssetFile } from './AssetFile.js'
|
|
10
|
-
import { AssetError } from '../errors/AssetError.js'
|
|
11
9
|
import { resolveFileUrl } from '../utils/asset.js'
|
|
12
10
|
|
|
13
11
|
const storageClasses = {}
|
|
@@ -82,21 +80,7 @@ export class Storage {
|
|
|
82
80
|
)
|
|
83
81
|
}
|
|
84
82
|
|
|
85
|
-
convertAssetFile(file
|
|
86
|
-
if (
|
|
87
|
-
!trusted &&
|
|
88
|
-
!(file instanceof AssetFile) &&
|
|
89
|
-
!this.isImportSourceAllowed(file.url) &&
|
|
90
|
-
!this.verifyAssetFile(file)
|
|
91
|
-
) {
|
|
92
|
-
throw new AssetError(
|
|
93
|
-
`Invalid asset signature for file '${
|
|
94
|
-
file.name ?? file.key
|
|
95
|
-
}'`
|
|
96
|
-
)
|
|
97
|
-
}
|
|
98
|
-
// Remove signature once the data made it to here.
|
|
99
|
-
delete file.signature
|
|
83
|
+
convertAssetFile(file) {
|
|
100
84
|
AssetFile.convert(file, this)
|
|
101
85
|
}
|
|
102
86
|
|
|
@@ -110,8 +94,7 @@ export class Storage {
|
|
|
110
94
|
url: this._getFileUrl(storageFile),
|
|
111
95
|
// In case `config.readDimensions` is set:
|
|
112
96
|
width: storageFile.width,
|
|
113
|
-
height: storageFile.height
|
|
114
|
-
signature: this._signAssetKey(storageFile.key)
|
|
97
|
+
height: storageFile.height
|
|
115
98
|
}
|
|
116
99
|
}
|
|
117
100
|
|
|
@@ -125,7 +108,7 @@ export class Storage {
|
|
|
125
108
|
file.url = this._getFileUrl(file)
|
|
126
109
|
// TODO: Support `config.readDimensions`, but this can only be done once
|
|
127
110
|
// there are separate storage instances per model assets config!
|
|
128
|
-
this.convertAssetFile(file
|
|
111
|
+
this.convertAssetFile(file)
|
|
129
112
|
return file
|
|
130
113
|
}
|
|
131
114
|
|
|
@@ -149,34 +132,6 @@ export class Storage {
|
|
|
149
132
|
return this._getFileUrl(file)
|
|
150
133
|
}
|
|
151
134
|
|
|
152
|
-
signAssetFile(file) {
|
|
153
|
-
file.signature = this._signAssetKey(file.key)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
verifyAssetFile(file) {
|
|
157
|
-
if (file) {
|
|
158
|
-
const expected = this._signAssetKey(file.key)
|
|
159
|
-
try {
|
|
160
|
-
return crypto.timingSafeEqual(
|
|
161
|
-
Buffer.from(expected, 'hex'),
|
|
162
|
-
Buffer.from(file.signature, 'hex')
|
|
163
|
-
)
|
|
164
|
-
} catch {}
|
|
165
|
-
}
|
|
166
|
-
return false
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
_signAssetKey(key) {
|
|
170
|
-
const secret = (
|
|
171
|
-
this.app.keys?.[0] ??
|
|
172
|
-
(Storage._fallbackSecret ??= crypto.randomBytes(32))
|
|
173
|
-
)
|
|
174
|
-
return crypto
|
|
175
|
-
.createHmac('sha256', secret)
|
|
176
|
-
.update(key)
|
|
177
|
-
.digest('hex')
|
|
178
|
-
}
|
|
179
|
-
|
|
180
135
|
_getUrl(...parts) {
|
|
181
136
|
return this.url
|
|
182
137
|
? new URL(path.posix.join(...parts), this.url).toString()
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { Storage } from './Storage.js'
|
|
2
|
-
|
|
3
|
-
function createStorage(keys = ['test-secret']) {
|
|
4
|
-
const app = { keys }
|
|
5
|
-
return new Storage(app, { name: 'test' })
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function simulateUpload(storage, key, originalname) {
|
|
9
|
-
return storage.convertStorageFile({
|
|
10
|
-
key,
|
|
11
|
-
originalname,
|
|
12
|
-
mimetype: 'image/png',
|
|
13
|
-
size: 2048
|
|
14
|
-
})
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
describe('Storage: asset key signature verification', () => {
|
|
18
|
-
it('preserves the key through a full upload-then-save cycle', () => {
|
|
19
|
-
const storage = createStorage()
|
|
20
|
-
const uploaded = simulateUpload(storage, 'real-key.png', 'photo.png')
|
|
21
|
-
const fromClient = { ...uploaded }
|
|
22
|
-
storage.convertAssetFile(fromClient, { trusted: false })
|
|
23
|
-
expect(fromClient.key).toBe('real-key.png')
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('throws when a forged key is submitted', () => {
|
|
27
|
-
const storage = createStorage()
|
|
28
|
-
const file = { key: 'victim-key.png', name: 'photo.png' }
|
|
29
|
-
expect(() => {
|
|
30
|
-
storage.convertAssetFile(file, { trusted: false })
|
|
31
|
-
}).toThrow('Invalid asset signature')
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it('throws when a valid signature is paired with a swapped key', () => {
|
|
35
|
-
const storage = createStorage()
|
|
36
|
-
const uploaded = simulateUpload(storage, 'legit.png', 'legit.png')
|
|
37
|
-
const forged = { ...uploaded, key: 'victim-file.png' }
|
|
38
|
-
expect(() => {
|
|
39
|
-
storage.convertAssetFile(forged, { trusted: false })
|
|
40
|
-
}).toThrow('Invalid asset signature')
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('does not throw when addFile() converts a server-created file', async () => {
|
|
44
|
-
const storage = createStorage()
|
|
45
|
-
const file = { key: 'imported.png', name: 'import.png' }
|
|
46
|
-
const data = Buffer.from('fake-image-data')
|
|
47
|
-
await storage.addFile(file, data)
|
|
48
|
-
expect(file.key).toBe('imported.png')
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('strips signature after conversion', () => {
|
|
52
|
-
const storage = createStorage()
|
|
53
|
-
const uploaded = simulateUpload(storage, 'upload.png', 'photo.png')
|
|
54
|
-
const fromClient = { ...uploaded }
|
|
55
|
-
storage.convertAssetFile(fromClient, { trusted: false })
|
|
56
|
-
expect('signature' in fromClient).toBe(false)
|
|
57
|
-
})
|
|
58
|
-
})
|