@kkuffour/solid-moderation-plugin 0.1.1 → 0.2.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/ARCHITECTURE.md +52 -0
- package/TESTING.md +75 -46
- package/components/components.jsonld +9 -8
- package/components/context.jsonld +39 -0
- package/config/default.json +26 -67
- package/dist/ModerationConfig.jsonld +2 -2
- package/dist/ModerationMixin.jsonld +2 -2
- package/dist/ModerationOperationHandler.jsonld +2 -2
- package/dist/ModerationRecord.jsonld +2 -2
- package/dist/ModerationResourceStore.d.ts +30 -0
- package/dist/ModerationResourceStore.d.ts.map +1 -0
- package/dist/ModerationResourceStore.js +167 -0
- package/dist/ModerationResourceStore.js.map +1 -0
- package/dist/ModerationResourceStore.jsonld +157 -0
- package/dist/ModerationStore.jsonld +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/providers/SightEngineProvider.jsonld +2 -2
- package/package.json +3 -3
- package/src/ModerationResourceStore.ts +227 -0
- package/src/index.ts +1 -0
package/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Architecture Decision: ResourceStore vs OperationHandler
|
|
2
|
+
|
|
3
|
+
## Branch History
|
|
4
|
+
|
|
5
|
+
### `main` branch (v0.1.0 - v0.1.2)
|
|
6
|
+
**Approach**: OperationHandler wrapping
|
|
7
|
+
**Status**: ❌ Does not work
|
|
8
|
+
**Reason**: Components.js cannot resolve cross-module component overrides. When trying to override `urn:solid-server:default:PutOperationHandler` with a type from an external npm package, Components.js looks for the type in the CSS module, not the plugin module.
|
|
9
|
+
|
|
10
|
+
**Key Learning**: Components.js uses module-scoped component resolution. External plugins cannot override core CSS handler IDs.
|
|
11
|
+
|
|
12
|
+
### `resourcestore-approach` branch (v0.2.0+)
|
|
13
|
+
**Approach**: ResourceStore wrapping
|
|
14
|
+
**Status**: 🚧 In development
|
|
15
|
+
**Strategy**: Wrap `urn:solid-server:default:ResourceStore` instead of individual operation handlers.
|
|
16
|
+
|
|
17
|
+
## Why ResourceStore Works
|
|
18
|
+
|
|
19
|
+
1. **Single interception point**: All write operations (PUT/POST/PATCH) call `ResourceStore.setRepresentation()`
|
|
20
|
+
2. **Designed for swapping**: CSS architecture expects ResourceStore to be configurable/replaceable
|
|
21
|
+
3. **Proper abstraction boundary**: ResourceStore is the storage layer interface
|
|
22
|
+
4. **Components.js compatible**: ResourceStore wrapping is a supported pattern
|
|
23
|
+
|
|
24
|
+
## Implementation Plan
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
Request Flow:
|
|
28
|
+
HTTP Request → OperationHandler → ResourceStore → Backend
|
|
29
|
+
↑
|
|
30
|
+
INTERCEPT HERE
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Core Changes
|
|
34
|
+
- Create `ModerationResourceStore` implementing `ResourceStore` interface
|
|
35
|
+
- Wrap original store and intercept `setRepresentation()`
|
|
36
|
+
- Moderate content before passing to wrapped store
|
|
37
|
+
- Delegate all read operations (getRepresentation, etc.)
|
|
38
|
+
|
|
39
|
+
### Config Pattern
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"@id": "urn:solid-server:default:ResourceStore",
|
|
43
|
+
"@type": "ModerationResourceStore",
|
|
44
|
+
"source": { "@id": "urn:solid-server:default:ResourceStore_Original" }
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Version Strategy
|
|
49
|
+
|
|
50
|
+
- **v0.1.x**: OperationHandler approach (archived, doesn't work)
|
|
51
|
+
- **v0.2.x**: ResourceStore approach (current development)
|
|
52
|
+
- Users can install specific versions if needed: `npm install @kkuffour/solid-moderation-plugin@0.1.2`
|
package/TESTING.md
CHANGED
|
@@ -1,64 +1,93 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Testing ResourceStore Approach Locally
|
|
2
2
|
|
|
3
3
|
## Setup
|
|
4
4
|
|
|
5
|
-
1.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
2. **Link the plugin locally (run in plugin directory):**
|
|
12
|
-
```bash
|
|
13
|
-
cd /Users/opendata/solid-moderation
|
|
14
|
-
npm link
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
3. **In your CSS project directory, link the plugin:**
|
|
18
|
-
```bash
|
|
19
|
-
cd /path/to/your/communitysolidserver
|
|
20
|
-
npm link @solid/moderation-plugin
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## Run CSS with Moderation
|
|
5
|
+
1. Build the plugin:
|
|
6
|
+
```bash
|
|
7
|
+
npm run build
|
|
8
|
+
```
|
|
24
9
|
|
|
10
|
+
2. Link locally for testing:
|
|
25
11
|
```bash
|
|
26
|
-
|
|
12
|
+
npm link
|
|
27
13
|
```
|
|
28
14
|
|
|
29
|
-
|
|
15
|
+
3. In your CSS directory:
|
|
16
|
+
```bash
|
|
17
|
+
npm link @kkuffour/solid-moderation-plugin
|
|
18
|
+
```
|
|
30
19
|
|
|
31
|
-
|
|
20
|
+
4. Set environment variables:
|
|
32
21
|
```bash
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
--data-binary @safe-image.jpg
|
|
22
|
+
export SIGHTENGINE_API_USER=1060049443
|
|
23
|
+
export SIGHTENGINE_API_SECRET=QRQ8HUmh4hyvhZjksBJq5ZaNYPLPEKXu
|
|
24
|
+
```
|
|
37
25
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
26
|
+
5. Create test config in CSS directory (`config-moderation-test.json`):
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"@context": [
|
|
30
|
+
"https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld",
|
|
31
|
+
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.2.0/components/context.jsonld"
|
|
32
|
+
],
|
|
33
|
+
"import": [
|
|
34
|
+
"css:config/file.json"
|
|
35
|
+
],
|
|
36
|
+
"@graph": [
|
|
37
|
+
{
|
|
38
|
+
"@id": "urn:solid-moderation:SightEngineProvider",
|
|
39
|
+
"@type": "ksmp:dist/providers/SightEngineProvider.jsonld#SightEngineProvider",
|
|
40
|
+
"ksmp:dist/providers/SightEngineProvider.jsonld#SightEngineProvider_apiUser": "1060049443",
|
|
41
|
+
"ksmp:dist/providers/SightEngineProvider.jsonld#SightEngineProvider_apiSecret": "QRQ8HUmh4hyvhZjksBJq5ZaNYPLPEKXu"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"@id": "urn:solid-server:default:ResourceStore_Original",
|
|
45
|
+
"@type": "DataAccessorBasedStore",
|
|
46
|
+
"identifierStrategy": { "@id": "urn:solid-server:default:IdentifierStrategy" },
|
|
47
|
+
"auxiliaryStrategy": { "@id": "urn:solid-server:default:AuxiliaryStrategy" },
|
|
48
|
+
"accessor": { "@id": "urn:solid-server:default:DataAccessor" },
|
|
49
|
+
"metadataStrategy": { "@id": "urn:solid-server:default:MetadataStrategy" }
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"@id": "urn:solid-server:default:ResourceStore",
|
|
53
|
+
"@type": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore",
|
|
54
|
+
"ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_source": {
|
|
55
|
+
"@id": "urn:solid-server:default:ResourceStore_Original"
|
|
56
|
+
},
|
|
57
|
+
"ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_client": {
|
|
58
|
+
"@id": "urn:solid-moderation:SightEngineProvider"
|
|
59
|
+
},
|
|
60
|
+
"ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_enabled": true,
|
|
61
|
+
"ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_imageNudityThreshold": 0.1,
|
|
62
|
+
"ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_textSexualThreshold": 0.1,
|
|
63
|
+
"ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_textToxicThreshold": 0.1,
|
|
64
|
+
"ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_videoNudityThreshold": 0.1
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
}
|
|
42
68
|
```
|
|
43
69
|
|
|
44
|
-
|
|
70
|
+
6. Start CSS:
|
|
45
71
|
```bash
|
|
46
|
-
|
|
47
|
-
curl -X PUT http://localhost:3000/test.txt \
|
|
48
|
-
-H "Content-Type: text/plain" \
|
|
49
|
-
-d "Hello, this is safe content"
|
|
50
|
-
|
|
51
|
-
# Upload inappropriate text (should be blocked)
|
|
52
|
-
curl -X PUT http://localhost:3000/test2.txt \
|
|
53
|
-
-H "Content-Type: text/plain" \
|
|
54
|
-
-d "inappropriate content here"
|
|
72
|
+
npx @solid/community-server -c config-moderation-test.json
|
|
55
73
|
```
|
|
56
74
|
|
|
57
|
-
##
|
|
75
|
+
## Expected Behavior
|
|
58
76
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
77
|
+
- Server should start without errors
|
|
78
|
+
- Log should show: "ModerationResourceStore initialized"
|
|
79
|
+
- Uploading images/videos/text should trigger moderation
|
|
80
|
+
- Content exceeding thresholds should be blocked with 403 Forbidden
|
|
81
|
+
|
|
82
|
+
## Testing
|
|
83
|
+
|
|
84
|
+
Upload a test image:
|
|
85
|
+
```bash
|
|
86
|
+
curl -X PUT http://localhost:3000/test.jpg \
|
|
87
|
+
-H "Content-Type: image/jpeg" \
|
|
88
|
+
--data-binary @test-image.jpg
|
|
62
89
|
```
|
|
63
90
|
|
|
64
|
-
|
|
91
|
+
Check logs for:
|
|
92
|
+
- "Moderating image/jpeg upload to /test.jpg"
|
|
93
|
+
- "Image APPROVED" or "Image BLOCKED"
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"@context": [
|
|
3
|
-
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.
|
|
4
|
-
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.
|
|
3
|
+
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.2.0/components/context.jsonld",
|
|
4
|
+
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.2.0/config/context.jsonld"
|
|
5
5
|
],
|
|
6
6
|
"@id": "npmd:@kkuffour/solid-moderation-plugin",
|
|
7
7
|
"@type": "Module",
|
|
8
8
|
"requireName": "@kkuffour/solid-moderation-plugin",
|
|
9
9
|
"import": [
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
10
|
+
"npmd:@solid/moderation-plugin/^1.0.0/components/ModerationOperationHandler.jsonld",
|
|
11
|
+
"npmd:@solid/moderation-plugin/^1.0.0/components/ModerationResourceStore.jsonld",
|
|
12
|
+
"npmd:@solid/moderation-plugin/^1.0.0/components/ModerationConfig.jsonld",
|
|
13
|
+
"npmd:@solid/moderation-plugin/^1.0.0/components/ModerationStore.jsonld",
|
|
14
|
+
"npmd:@solid/moderation-plugin/^1.0.0/components/ModerationRecord.jsonld",
|
|
15
|
+
"npmd:@solid/moderation-plugin/^1.0.0/components/ModerationMixin.jsonld",
|
|
16
|
+
"npmd:@solid/moderation-plugin/^1.0.0/components/providers/SightEngineProvider.jsonld"
|
|
16
17
|
]
|
|
17
18
|
}
|
|
@@ -82,6 +82,45 @@
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
},
|
|
85
|
+
"ModerationResourceStore": {
|
|
86
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore",
|
|
87
|
+
"@prefix": true,
|
|
88
|
+
"@context": {
|
|
89
|
+
"enabled": {
|
|
90
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_enabled"
|
|
91
|
+
},
|
|
92
|
+
"imageNudityThreshold": {
|
|
93
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_imageNudityThreshold"
|
|
94
|
+
},
|
|
95
|
+
"textSexualThreshold": {
|
|
96
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_textSexualThreshold"
|
|
97
|
+
},
|
|
98
|
+
"textToxicThreshold": {
|
|
99
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_textToxicThreshold"
|
|
100
|
+
},
|
|
101
|
+
"videoNudityThreshold": {
|
|
102
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_videoNudityThreshold"
|
|
103
|
+
},
|
|
104
|
+
"client": {
|
|
105
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_client"
|
|
106
|
+
},
|
|
107
|
+
"source": {
|
|
108
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_source"
|
|
109
|
+
},
|
|
110
|
+
"": {
|
|
111
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_enabled"
|
|
112
|
+
},
|
|
113
|
+
"dityThreshold": {
|
|
114
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_videoNudityThreshold"
|
|
115
|
+
},
|
|
116
|
+
"ualThreshold": {
|
|
117
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_textSexualThreshold"
|
|
118
|
+
},
|
|
119
|
+
"icThreshold": {
|
|
120
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_textToxicThreshold"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
},
|
|
85
124
|
"ModerationConfig": {
|
|
86
125
|
"@id": "ksmp:dist/ModerationConfig.jsonld#ModerationConfig",
|
|
87
126
|
"@prefix": true,
|
package/config/default.json
CHANGED
|
@@ -1,80 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"@context": [
|
|
3
3
|
"https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld",
|
|
4
|
-
"https://linkedsoftwaredependencies.org/bundles/npm/@solid
|
|
4
|
+
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.2.0/components/context.jsonld"
|
|
5
|
+
],
|
|
6
|
+
"import": [
|
|
7
|
+
"css:config/file.json"
|
|
5
8
|
],
|
|
6
9
|
"@graph": [
|
|
7
10
|
{
|
|
8
|
-
"@
|
|
9
|
-
"@
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
{ "@id": "urn:solid-moderation:wrapped:PatchHandler" },
|
|
14
|
-
{ "@type": "GetOperationHandler", "store": { "@id": "urn:solid-server:default:ResourceStore" } },
|
|
15
|
-
{ "@type": "HeadOperationHandler", "store": { "@id": "urn:solid-server:default:ResourceStore" } },
|
|
16
|
-
{ "@type": "DeleteOperationHandler", "store": { "@id": "urn:solid-server:default:ResourceStore" } }
|
|
17
|
-
]
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
"@id": "urn:solid-moderation:wrapped:PostHandler",
|
|
21
|
-
"@type": "ModerationOperationHandler",
|
|
22
|
-
"ModerationOperationHandler:_source": {
|
|
23
|
-
"@type": "PostOperationHandler",
|
|
24
|
-
"store": { "@id": "urn:solid-server:default:ResourceStore" }
|
|
25
|
-
},
|
|
26
|
-
"ModerationOperationHandler:_enabled": true,
|
|
27
|
-
"ModerationOperationHandler:_auditLoggingEnabled": true,
|
|
28
|
-
"ModerationOperationHandler:_auditLoggingStorePath": "./data/moderation-logs",
|
|
29
|
-
"ModerationOperationHandler:_sightEngineApiUser": "${SIGHTENGINE_API_USER}",
|
|
30
|
-
"ModerationOperationHandler:_sightEngineApiSecret": "${SIGHTENGINE_API_SECRET}",
|
|
31
|
-
"ModerationOperationHandler:_imagesEnabled": true,
|
|
32
|
-
"ModerationOperationHandler:_textEnabled": true,
|
|
33
|
-
"ModerationOperationHandler:_videoEnabled": true,
|
|
34
|
-
"ModerationOperationHandler:_imageNudityThreshold": 0.5,
|
|
35
|
-
"ModerationOperationHandler:_textSexualThreshold": 0.5,
|
|
36
|
-
"ModerationOperationHandler:_textToxicThreshold": 0.5,
|
|
37
|
-
"ModerationOperationHandler:_videoNudityThreshold": 0.5
|
|
11
|
+
"@comment": "SightEngine API client",
|
|
12
|
+
"@id": "urn:solid-moderation:SightEngineProvider",
|
|
13
|
+
"@type": "SightEngineProvider",
|
|
14
|
+
"SightEngineProvider:_apiUser": "${SIGHTENGINE_API_USER}",
|
|
15
|
+
"SightEngineProvider:_apiSecret": "${SIGHTENGINE_API_SECRET}"
|
|
38
16
|
},
|
|
39
17
|
{
|
|
40
|
-
"@
|
|
41
|
-
"@
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
},
|
|
46
|
-
"
|
|
47
|
-
"ModerationOperationHandler:_auditLoggingEnabled": true,
|
|
48
|
-
"ModerationOperationHandler:_auditLoggingStorePath": "./data/moderation-logs",
|
|
49
|
-
"ModerationOperationHandler:_sightEngineApiUser": "${SIGHTENGINE_API_USER}",
|
|
50
|
-
"ModerationOperationHandler:_sightEngineApiSecret": "${SIGHTENGINE_API_SECRET}",
|
|
51
|
-
"ModerationOperationHandler:_imagesEnabled": true,
|
|
52
|
-
"ModerationOperationHandler:_textEnabled": true,
|
|
53
|
-
"ModerationOperationHandler:_videoEnabled": true,
|
|
54
|
-
"ModerationOperationHandler:_imageNudityThreshold": 0.5,
|
|
55
|
-
"ModerationOperationHandler:_textSexualThreshold": 0.5,
|
|
56
|
-
"ModerationOperationHandler:_textToxicThreshold": 0.5,
|
|
57
|
-
"ModerationOperationHandler:_videoNudityThreshold": 0.5
|
|
18
|
+
"@comment": "Original ResourceStore - renamed to avoid conflict",
|
|
19
|
+
"@id": "urn:solid-server:default:ResourceStore_Original",
|
|
20
|
+
"@type": "DataAccessorBasedStore",
|
|
21
|
+
"identifierStrategy": { "@id": "urn:solid-server:default:IdentifierStrategy" },
|
|
22
|
+
"auxiliaryStrategy": { "@id": "urn:solid-server:default:AuxiliaryStrategy" },
|
|
23
|
+
"accessor": { "@id": "urn:solid-server:default:DataAccessor" },
|
|
24
|
+
"metadataStrategy": { "@id": "urn:solid-server:default:MetadataStrategy" }
|
|
58
25
|
},
|
|
59
26
|
{
|
|
60
|
-
"@
|
|
61
|
-
"@
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"ModerationOperationHandler:_sightEngineApiSecret": "${SIGHTENGINE_API_SECRET}",
|
|
71
|
-
"ModerationOperationHandler:_imagesEnabled": true,
|
|
72
|
-
"ModerationOperationHandler:_textEnabled": true,
|
|
73
|
-
"ModerationOperationHandler:_videoEnabled": true,
|
|
74
|
-
"ModerationOperationHandler:_imageNudityThreshold": 0.5,
|
|
75
|
-
"ModerationOperationHandler:_textSexualThreshold": 0.5,
|
|
76
|
-
"ModerationOperationHandler:_textToxicThreshold": 0.5,
|
|
77
|
-
"ModerationOperationHandler:_videoNudityThreshold": 0.5
|
|
27
|
+
"@comment": "Moderation wrapper for ResourceStore",
|
|
28
|
+
"@id": "urn:solid-server:default:ResourceStore",
|
|
29
|
+
"@type": "ModerationResourceStore",
|
|
30
|
+
"ModerationResourceStore:_source": { "@id": "urn:solid-server:default:ResourceStore_Original" },
|
|
31
|
+
"ModerationResourceStore:_client": { "@id": "urn:solid-moderation:SightEngineProvider" },
|
|
32
|
+
"ModerationResourceStore:_enabled": true,
|
|
33
|
+
"ModerationResourceStore:_imageNudityThreshold": 0.5,
|
|
34
|
+
"ModerationResourceStore:_textSexualThreshold": 0.5,
|
|
35
|
+
"ModerationResourceStore:_textToxicThreshold": 0.5,
|
|
36
|
+
"ModerationResourceStore:_videoNudityThreshold": 0.5
|
|
78
37
|
}
|
|
79
38
|
]
|
|
80
39
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"@context": [
|
|
3
|
-
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.
|
|
4
|
-
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.
|
|
3
|
+
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.2.0/components/context.jsonld",
|
|
4
|
+
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.2.0/config/context.jsonld"
|
|
5
5
|
],
|
|
6
6
|
"@id": "npmd:@kkuffour/solid-moderation-plugin",
|
|
7
7
|
"components": [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"@context": [
|
|
3
|
-
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.
|
|
4
|
-
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.
|
|
3
|
+
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.2.0/components/context.jsonld",
|
|
4
|
+
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.2.0/config/context.jsonld"
|
|
5
5
|
],
|
|
6
6
|
"@id": "npmd:@kkuffour/solid-moderation-plugin",
|
|
7
7
|
"components": [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"@context": [
|
|
3
|
-
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.
|
|
4
|
-
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.
|
|
3
|
+
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.2.0/components/context.jsonld",
|
|
4
|
+
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.2.0/config/context.jsonld",
|
|
5
5
|
"https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld"
|
|
6
6
|
],
|
|
7
7
|
"@id": "npmd:@kkuffour/solid-moderation-plugin",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"@context": [
|
|
3
|
-
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.
|
|
4
|
-
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.
|
|
3
|
+
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.2.0/components/context.jsonld",
|
|
4
|
+
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.2.0/config/context.jsonld"
|
|
5
5
|
],
|
|
6
6
|
"@id": "npmd:@kkuffour/solid-moderation-plugin",
|
|
7
7
|
"components": [
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ResourceStore, ChangeMap, Representation, ResourceIdentifier, RepresentationPreferences, Conditions, Patch } from '@solid/community-server';
|
|
2
|
+
import type { SightEngineProvider } from './providers/SightEngineProvider';
|
|
3
|
+
/**
|
|
4
|
+
* @module
|
|
5
|
+
* Wraps a ResourceStore to add content moderation on write operations.
|
|
6
|
+
*/
|
|
7
|
+
export declare class ModerationResourceStore implements ResourceStore {
|
|
8
|
+
private readonly logger;
|
|
9
|
+
private readonly source;
|
|
10
|
+
private readonly client;
|
|
11
|
+
private readonly enabled;
|
|
12
|
+
private readonly imageNudityThreshold;
|
|
13
|
+
private readonly textSexualThreshold;
|
|
14
|
+
private readonly textToxicThreshold;
|
|
15
|
+
private readonly videoNudityThreshold;
|
|
16
|
+
constructor(source: ResourceStore, client: SightEngineProvider, enabled: boolean, imageNudityThreshold: number, textSexualThreshold: number, textToxicThreshold: number, videoNudityThreshold: number);
|
|
17
|
+
hasResource(identifier: ResourceIdentifier): Promise<boolean>;
|
|
18
|
+
getRepresentation(identifier: ResourceIdentifier, preferences: RepresentationPreferences, conditions?: Conditions): Promise<Representation>;
|
|
19
|
+
setRepresentation(identifier: ResourceIdentifier, representation: Representation, conditions?: Conditions): Promise<ChangeMap>;
|
|
20
|
+
addResource(container: ResourceIdentifier, representation: Representation, conditions?: Conditions): Promise<ChangeMap>;
|
|
21
|
+
deleteResource(identifier: ResourceIdentifier, conditions?: Conditions): Promise<ChangeMap>;
|
|
22
|
+
modifyResource(identifier: ResourceIdentifier, patch: Patch, conditions?: Conditions): Promise<ChangeMap>;
|
|
23
|
+
private moderateRepresentation;
|
|
24
|
+
private shouldModerateContentType;
|
|
25
|
+
private moderateImage;
|
|
26
|
+
private moderateVideo;
|
|
27
|
+
private moderateText;
|
|
28
|
+
private getFileExtension;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=ModerationResourceStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ModerationResourceStore.d.ts","sourceRoot":"","sources":["../src/ModerationResourceStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,cAAc,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAG1J,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAE3E;;;GAGG;AACH,qBAAa,uBAAwB,YAAW,aAAa;IAC3D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAC7C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAS;IAC9C,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IAC7C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;IAC5C,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAS;gBAG5C,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,OAAO,EAChB,oBAAoB,EAAE,MAAM,EAC5B,mBAAmB,EAAE,MAAM,EAC3B,kBAAkB,EAAE,MAAM,EAC1B,oBAAoB,EAAE,MAAM;IAejB,WAAW,CAAC,UAAU,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IAI7D,iBAAiB,CAC5B,UAAU,EAAE,kBAAkB,EAC9B,WAAW,EAAE,yBAAyB,EACtC,UAAU,CAAC,EAAE,UAAU,GACtB,OAAO,CAAC,cAAc,CAAC;IAIb,iBAAiB,CAC5B,UAAU,EAAE,kBAAkB,EAC9B,cAAc,EAAE,cAAc,EAC9B,UAAU,CAAC,EAAE,UAAU,GACtB,OAAO,CAAC,SAAS,CAAC;IAKR,WAAW,CACtB,SAAS,EAAE,kBAAkB,EAC7B,cAAc,EAAE,cAAc,EAC9B,UAAU,CAAC,EAAE,UAAU,GACtB,OAAO,CAAC,SAAS,CAAC;IAKR,cAAc,CACzB,UAAU,EAAE,kBAAkB,EAC9B,UAAU,CAAC,EAAE,UAAU,GACtB,OAAO,CAAC,SAAS,CAAC;IAIR,cAAc,CACzB,UAAU,EAAE,kBAAkB,EAC9B,KAAK,EAAE,KAAK,EACZ,UAAU,CAAC,EAAE,UAAU,GACtB,OAAO,CAAC,SAAS,CAAC;YAIP,sBAAsB;IAyDpC,OAAO,CAAC,yBAAyB;YAMnB,aAAa;YAsBb,aAAa;YAsBb,YAAY;IAsB1B,OAAO,CAAC,gBAAgB;CAWzB"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ModerationResourceStore = void 0;
|
|
4
|
+
const community_server_1 = require("@solid/community-server");
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
/**
|
|
7
|
+
* @module
|
|
8
|
+
* Wraps a ResourceStore to add content moderation on write operations.
|
|
9
|
+
*/
|
|
10
|
+
class ModerationResourceStore {
|
|
11
|
+
constructor(source, client, enabled, imageNudityThreshold, textSexualThreshold, textToxicThreshold, videoNudityThreshold) {
|
|
12
|
+
this.logger = (0, community_server_1.getLoggerFor)(this);
|
|
13
|
+
this.source = source;
|
|
14
|
+
this.client = client;
|
|
15
|
+
this.enabled = enabled;
|
|
16
|
+
this.imageNudityThreshold = imageNudityThreshold;
|
|
17
|
+
this.textSexualThreshold = textSexualThreshold;
|
|
18
|
+
this.textToxicThreshold = textToxicThreshold;
|
|
19
|
+
this.videoNudityThreshold = videoNudityThreshold;
|
|
20
|
+
this.logger.info('ModerationResourceStore initialized');
|
|
21
|
+
this.logger.info(` Enabled: ${enabled}`);
|
|
22
|
+
this.logger.info(` Thresholds: image=${imageNudityThreshold}, text=${textSexualThreshold}/${textToxicThreshold}, video=${videoNudityThreshold}`);
|
|
23
|
+
}
|
|
24
|
+
async hasResource(identifier) {
|
|
25
|
+
return this.source.hasResource(identifier);
|
|
26
|
+
}
|
|
27
|
+
async getRepresentation(identifier, preferences, conditions) {
|
|
28
|
+
return this.source.getRepresentation(identifier, preferences, conditions);
|
|
29
|
+
}
|
|
30
|
+
async setRepresentation(identifier, representation, conditions) {
|
|
31
|
+
await this.moderateRepresentation(identifier, representation);
|
|
32
|
+
return this.source.setRepresentation(identifier, representation, conditions);
|
|
33
|
+
}
|
|
34
|
+
async addResource(container, representation, conditions) {
|
|
35
|
+
await this.moderateRepresentation(container, representation);
|
|
36
|
+
return this.source.addResource(container, representation, conditions);
|
|
37
|
+
}
|
|
38
|
+
async deleteResource(identifier, conditions) {
|
|
39
|
+
return this.source.deleteResource(identifier, conditions);
|
|
40
|
+
}
|
|
41
|
+
async modifyResource(identifier, patch, conditions) {
|
|
42
|
+
return this.source.modifyResource(identifier, patch, conditions);
|
|
43
|
+
}
|
|
44
|
+
async moderateRepresentation(identifier, representation) {
|
|
45
|
+
if (!this.enabled || !representation.data) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const contentType = representation.metadata.contentType;
|
|
49
|
+
if (!contentType) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const shouldModerate = this.shouldModerateContentType(contentType);
|
|
53
|
+
if (!shouldModerate) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
this.logger.info(`Moderating ${contentType} upload to ${identifier.path}`);
|
|
57
|
+
try {
|
|
58
|
+
const chunks = [];
|
|
59
|
+
for await (const chunk of representation.data) {
|
|
60
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
61
|
+
}
|
|
62
|
+
const buffer = Buffer.concat(chunks);
|
|
63
|
+
if (contentType.startsWith('image/')) {
|
|
64
|
+
await this.moderateImage(identifier.path, buffer, contentType);
|
|
65
|
+
}
|
|
66
|
+
else if (contentType.startsWith('video/')) {
|
|
67
|
+
await this.moderateVideo(identifier.path, buffer, contentType);
|
|
68
|
+
}
|
|
69
|
+
else if (contentType.startsWith('text/')) {
|
|
70
|
+
await this.moderateText(identifier.path, buffer);
|
|
71
|
+
}
|
|
72
|
+
representation.data = (0, community_server_1.guardedStreamFrom)(buffer);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (error instanceof community_server_1.ForbiddenHttpError) {
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
this.logger.error(`Moderation failed for ${identifier.path}: ${error.message}`);
|
|
79
|
+
this.logger.warn('Allowing content through due to moderation failure (fail-open policy)');
|
|
80
|
+
// Re-create stream from buffer if we have it
|
|
81
|
+
const chunks = [];
|
|
82
|
+
try {
|
|
83
|
+
for await (const chunk of representation.data) {
|
|
84
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
85
|
+
}
|
|
86
|
+
representation.data = (0, community_server_1.guardedStreamFrom)(Buffer.concat(chunks));
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Stream already consumed, can't recover
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
shouldModerateContentType(contentType) {
|
|
94
|
+
return contentType.startsWith('image/') ||
|
|
95
|
+
contentType.startsWith('video/') ||
|
|
96
|
+
contentType.startsWith('text/');
|
|
97
|
+
}
|
|
98
|
+
async moderateImage(path, buffer, contentType) {
|
|
99
|
+
const tempFile = `/tmp/moderation_${Date.now()}.${this.getFileExtension(contentType)}`;
|
|
100
|
+
(0, fs_1.writeFileSync)(tempFile, buffer);
|
|
101
|
+
try {
|
|
102
|
+
const result = await this.client.analyzeImage(tempFile);
|
|
103
|
+
if (result.nudity?.raw && result.nudity.raw > this.imageNudityThreshold) {
|
|
104
|
+
this.logger.warn(`Image BLOCKED: ${path} - nudity score ${result.nudity.raw.toFixed(2)} > ${this.imageNudityThreshold}`);
|
|
105
|
+
throw new community_server_1.ForbiddenHttpError(`Image contains inappropriate content (nudity: ${result.nudity.raw.toFixed(2)})`);
|
|
106
|
+
}
|
|
107
|
+
this.logger.info(`Image APPROVED: ${path}`);
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
try {
|
|
111
|
+
(0, fs_1.unlinkSync)(tempFile);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
this.logger.warn(`Failed to cleanup temp file: ${tempFile}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async moderateVideo(path, buffer, contentType) {
|
|
119
|
+
const tempFile = `/tmp/moderation_${Date.now()}.${this.getFileExtension(contentType)}`;
|
|
120
|
+
(0, fs_1.writeFileSync)(tempFile, buffer);
|
|
121
|
+
try {
|
|
122
|
+
const result = await this.client.analyzeVideo(tempFile);
|
|
123
|
+
if (result.nudity?.raw && result.nudity.raw > this.videoNudityThreshold) {
|
|
124
|
+
this.logger.warn(`Video BLOCKED: ${path} - nudity score ${result.nudity.raw.toFixed(2)} > ${this.videoNudityThreshold}`);
|
|
125
|
+
throw new community_server_1.ForbiddenHttpError(`Video contains inappropriate content (nudity: ${result.nudity.raw.toFixed(2)})`);
|
|
126
|
+
}
|
|
127
|
+
this.logger.info(`Video APPROVED: ${path}`);
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
try {
|
|
131
|
+
(0, fs_1.unlinkSync)(tempFile);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
this.logger.warn(`Failed to cleanup temp file: ${tempFile}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async moderateText(path, buffer) {
|
|
139
|
+
const text = buffer.toString('utf-8');
|
|
140
|
+
const result = await this.client.analyzeText(text);
|
|
141
|
+
const violations = [];
|
|
142
|
+
if (result.sexual > this.textSexualThreshold) {
|
|
143
|
+
violations.push(`sexual content (${result.sexual.toFixed(2)} > ${this.textSexualThreshold})`);
|
|
144
|
+
}
|
|
145
|
+
if (result.toxic > this.textToxicThreshold) {
|
|
146
|
+
violations.push(`toxic content (${result.toxic.toFixed(2)} > ${this.textToxicThreshold})`);
|
|
147
|
+
}
|
|
148
|
+
if (violations.length > 0) {
|
|
149
|
+
this.logger.warn(`Text BLOCKED: ${path} - ${violations.join(', ')}`);
|
|
150
|
+
throw new community_server_1.ForbiddenHttpError(`Text contains inappropriate content: ${violations.join(', ')}`);
|
|
151
|
+
}
|
|
152
|
+
this.logger.info(`Text APPROVED: ${path}`);
|
|
153
|
+
}
|
|
154
|
+
getFileExtension(contentType) {
|
|
155
|
+
const map = {
|
|
156
|
+
'image/jpeg': 'jpg',
|
|
157
|
+
'image/png': 'png',
|
|
158
|
+
'image/gif': 'gif',
|
|
159
|
+
'image/webp': 'webp',
|
|
160
|
+
'video/mp4': 'mp4',
|
|
161
|
+
'video/webm': 'webm',
|
|
162
|
+
};
|
|
163
|
+
return map[contentType] || 'bin';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
exports.ModerationResourceStore = ModerationResourceStore;
|
|
167
|
+
//# sourceMappingURL=ModerationResourceStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ModerationResourceStore.js","sourceRoot":"","sources":["../src/ModerationResourceStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
{
|
|
2
|
+
"@context": [
|
|
3
|
+
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.2.0/components/context.jsonld",
|
|
4
|
+
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.2.0/config/context.jsonld",
|
|
5
|
+
"https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld"
|
|
6
|
+
],
|
|
7
|
+
"@id": "npmd:@kkuffour/solid-moderation-plugin",
|
|
8
|
+
"components": [
|
|
9
|
+
{
|
|
10
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore",
|
|
11
|
+
"@type": "Class",
|
|
12
|
+
"requireElement": "ModerationResourceStore",
|
|
13
|
+
"extends": [
|
|
14
|
+
"css:dist/storage/ResourceStore.jsonld#ResourceStore"
|
|
15
|
+
],
|
|
16
|
+
"parameters": [
|
|
17
|
+
{
|
|
18
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_enabled",
|
|
19
|
+
"range": "xsd:boolean"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_imageNudityThreshold",
|
|
23
|
+
"range": "xsd:number"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_textSexualThreshold",
|
|
27
|
+
"range": "xsd:number"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_textToxicThreshold",
|
|
31
|
+
"range": "xsd:number"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_videoNudityThreshold",
|
|
35
|
+
"range": "xsd:number"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_client",
|
|
39
|
+
"range": "ksmp:dist/providers/SightEngineProvider.jsonld#SightEngineProvider"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_source",
|
|
43
|
+
"range": "css:dist/storage/ResourceStore.jsonld#ResourceStore"
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"memberFields": [
|
|
47
|
+
{
|
|
48
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore__member_logger",
|
|
49
|
+
"memberFieldName": "logger"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore__member_source",
|
|
53
|
+
"memberFieldName": "source"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore__member_client",
|
|
57
|
+
"memberFieldName": "client"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore__member_enabled",
|
|
61
|
+
"memberFieldName": "enabled"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore__member_imageNudityThreshold",
|
|
65
|
+
"memberFieldName": "imageNudityThreshold"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore__member_textSexualThreshold",
|
|
69
|
+
"memberFieldName": "textSexualThreshold"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore__member_textToxicThreshold",
|
|
73
|
+
"memberFieldName": "textToxicThreshold"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore__member_videoNudityThreshold",
|
|
77
|
+
"memberFieldName": "videoNudityThreshold"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore__member_constructor",
|
|
81
|
+
"memberFieldName": "constructor"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore__member_hasResource",
|
|
85
|
+
"memberFieldName": "hasResource"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore__member_getRepresentation",
|
|
89
|
+
"memberFieldName": "getRepresentation"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore__member_setRepresentation",
|
|
93
|
+
"memberFieldName": "setRepresentation"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore__member_addResource",
|
|
97
|
+
"memberFieldName": "addResource"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore__member_deleteResource",
|
|
101
|
+
"memberFieldName": "deleteResource"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore__member_modifyResource",
|
|
105
|
+
"memberFieldName": "modifyResource"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore__member_moderateRepresentation",
|
|
109
|
+
"memberFieldName": "moderateRepresentation"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore__member_shouldModerateContentType",
|
|
113
|
+
"memberFieldName": "shouldModerateContentType"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore__member_moderateImage",
|
|
117
|
+
"memberFieldName": "moderateImage"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore__member_moderateVideo",
|
|
121
|
+
"memberFieldName": "moderateVideo"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore__member_moderateText",
|
|
125
|
+
"memberFieldName": "moderateText"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore__member_getFileExtension",
|
|
129
|
+
"memberFieldName": "getFileExtension"
|
|
130
|
+
}
|
|
131
|
+
],
|
|
132
|
+
"constructorArguments": [
|
|
133
|
+
{
|
|
134
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_source"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_client"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_enabled"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_imageNudityThreshold"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_textSexualThreshold"
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_textToxicThreshold"
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"@id": "ksmp:dist/ModerationResourceStore.jsonld#ModerationResourceStore_videoNudityThreshold"
|
|
153
|
+
}
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"@context": [
|
|
3
|
-
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.
|
|
4
|
-
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.
|
|
3
|
+
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.2.0/components/context.jsonld",
|
|
4
|
+
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.2.0/config/context.jsonld"
|
|
5
5
|
],
|
|
6
6
|
"@id": "npmd:@kkuffour/solid-moderation-plugin",
|
|
7
7
|
"components": [
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAC7C,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iCAAiC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAC7C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iCAAiC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./ModerationOperationHandler"), exports);
|
|
18
|
+
__exportStar(require("./ModerationResourceStore"), exports);
|
|
18
19
|
__exportStar(require("./ModerationConfig"), exports);
|
|
19
20
|
__exportStar(require("./ModerationStore"), exports);
|
|
20
21
|
__exportStar(require("./ModerationRecord"), exports);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,+DAA6C;AAC7C,qDAAmC;AACnC,oDAAkC;AAClC,qDAAmC;AACnC,oDAAkC;AAClC,kEAAgD"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,+DAA6C;AAC7C,4DAA0C;AAC1C,qDAAmC;AACnC,oDAAkC;AAClC,qDAAmC;AACnC,oDAAkC;AAClC,kEAAgD"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"@context": [
|
|
3
|
-
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.
|
|
4
|
-
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.
|
|
3
|
+
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.2.0/components/context.jsonld",
|
|
4
|
+
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.2.0/config/context.jsonld"
|
|
5
5
|
],
|
|
6
6
|
"@id": "npmd:@kkuffour/solid-moderation-plugin",
|
|
7
7
|
"components": [
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kkuffour/solid-moderation-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Content moderation plugin for Community Solid Server",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"lsd:module": "https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin",
|
|
8
8
|
"lsd:components": "components/components.jsonld",
|
|
9
9
|
"lsd:contexts": {
|
|
10
|
-
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.
|
|
11
|
-
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.
|
|
10
|
+
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.2.0/components/context.jsonld": "components/context.jsonld",
|
|
11
|
+
"https://linkedsoftwaredependencies.org/bundles/npm/@kkuffour/solid-moderation-plugin/^0.2.0/config/context.jsonld": "config/context.jsonld"
|
|
12
12
|
},
|
|
13
13
|
"lsd:importPaths": {
|
|
14
14
|
"https://linkedsoftwaredependencies.org/bundles/npm/@solid/moderation-plugin/^1.0.0/components/": "dist/",
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import type { ResourceStore, ChangeMap, Representation, ResourceIdentifier, RepresentationPreferences, Conditions, Patch } from '@solid/community-server';
|
|
2
|
+
import { getLoggerFor, ForbiddenHttpError, guardedStreamFrom } from '@solid/community-server';
|
|
3
|
+
import { writeFileSync, unlinkSync } from 'fs';
|
|
4
|
+
import type { SightEngineProvider } from './providers/SightEngineProvider';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @module
|
|
8
|
+
* Wraps a ResourceStore to add content moderation on write operations.
|
|
9
|
+
*/
|
|
10
|
+
export class ModerationResourceStore implements ResourceStore {
|
|
11
|
+
private readonly logger = getLoggerFor(this);
|
|
12
|
+
private readonly source: ResourceStore;
|
|
13
|
+
private readonly client: SightEngineProvider;
|
|
14
|
+
private readonly enabled: boolean;
|
|
15
|
+
private readonly imageNudityThreshold: number;
|
|
16
|
+
private readonly textSexualThreshold: number;
|
|
17
|
+
private readonly textToxicThreshold: number;
|
|
18
|
+
private readonly videoNudityThreshold: number;
|
|
19
|
+
|
|
20
|
+
public constructor(
|
|
21
|
+
source: ResourceStore,
|
|
22
|
+
client: SightEngineProvider,
|
|
23
|
+
enabled: boolean,
|
|
24
|
+
imageNudityThreshold: number,
|
|
25
|
+
textSexualThreshold: number,
|
|
26
|
+
textToxicThreshold: number,
|
|
27
|
+
videoNudityThreshold: number,
|
|
28
|
+
) {
|
|
29
|
+
this.source = source;
|
|
30
|
+
this.client = client;
|
|
31
|
+
this.enabled = enabled;
|
|
32
|
+
this.imageNudityThreshold = imageNudityThreshold;
|
|
33
|
+
this.textSexualThreshold = textSexualThreshold;
|
|
34
|
+
this.textToxicThreshold = textToxicThreshold;
|
|
35
|
+
this.videoNudityThreshold = videoNudityThreshold;
|
|
36
|
+
|
|
37
|
+
this.logger.info('ModerationResourceStore initialized');
|
|
38
|
+
this.logger.info(` Enabled: ${enabled}`);
|
|
39
|
+
this.logger.info(` Thresholds: image=${imageNudityThreshold}, text=${textSexualThreshold}/${textToxicThreshold}, video=${videoNudityThreshold}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public async hasResource(identifier: ResourceIdentifier): Promise<boolean> {
|
|
43
|
+
return this.source.hasResource(identifier);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public async getRepresentation(
|
|
47
|
+
identifier: ResourceIdentifier,
|
|
48
|
+
preferences: RepresentationPreferences,
|
|
49
|
+
conditions?: Conditions,
|
|
50
|
+
): Promise<Representation> {
|
|
51
|
+
return this.source.getRepresentation(identifier, preferences, conditions);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public async setRepresentation(
|
|
55
|
+
identifier: ResourceIdentifier,
|
|
56
|
+
representation: Representation,
|
|
57
|
+
conditions?: Conditions,
|
|
58
|
+
): Promise<ChangeMap> {
|
|
59
|
+
await this.moderateRepresentation(identifier, representation);
|
|
60
|
+
return this.source.setRepresentation(identifier, representation, conditions);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public async addResource(
|
|
64
|
+
container: ResourceIdentifier,
|
|
65
|
+
representation: Representation,
|
|
66
|
+
conditions?: Conditions,
|
|
67
|
+
): Promise<ChangeMap> {
|
|
68
|
+
await this.moderateRepresentation(container, representation);
|
|
69
|
+
return this.source.addResource(container, representation, conditions);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public async deleteResource(
|
|
73
|
+
identifier: ResourceIdentifier,
|
|
74
|
+
conditions?: Conditions,
|
|
75
|
+
): Promise<ChangeMap> {
|
|
76
|
+
return this.source.deleteResource(identifier, conditions);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public async modifyResource(
|
|
80
|
+
identifier: ResourceIdentifier,
|
|
81
|
+
patch: Patch,
|
|
82
|
+
conditions?: Conditions,
|
|
83
|
+
): Promise<ChangeMap> {
|
|
84
|
+
return this.source.modifyResource(identifier, patch, conditions);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private async moderateRepresentation(
|
|
88
|
+
identifier: ResourceIdentifier,
|
|
89
|
+
representation: Representation,
|
|
90
|
+
): Promise<void> {
|
|
91
|
+
if (!this.enabled || !representation.data) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const contentType = representation.metadata.contentType;
|
|
96
|
+
if (!contentType) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const shouldModerate = this.shouldModerateContentType(contentType);
|
|
101
|
+
if (!shouldModerate) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.logger.info(`Moderating ${contentType} upload to ${identifier.path}`);
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const chunks: Buffer[] = [];
|
|
109
|
+
for await (const chunk of representation.data) {
|
|
110
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
111
|
+
}
|
|
112
|
+
const buffer = Buffer.concat(chunks);
|
|
113
|
+
|
|
114
|
+
if (contentType.startsWith('image/')) {
|
|
115
|
+
await this.moderateImage(identifier.path, buffer, contentType);
|
|
116
|
+
} else if (contentType.startsWith('video/')) {
|
|
117
|
+
await this.moderateVideo(identifier.path, buffer, contentType);
|
|
118
|
+
} else if (contentType.startsWith('text/')) {
|
|
119
|
+
await this.moderateText(identifier.path, buffer);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
representation.data = guardedStreamFrom(buffer);
|
|
123
|
+
|
|
124
|
+
} catch (error: unknown) {
|
|
125
|
+
if (error instanceof ForbiddenHttpError) {
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
this.logger.error(`Moderation failed for ${identifier.path}: ${(error as Error).message}`);
|
|
129
|
+
this.logger.warn('Allowing content through due to moderation failure (fail-open policy)');
|
|
130
|
+
|
|
131
|
+
// Re-create stream from buffer if we have it
|
|
132
|
+
const chunks: Buffer[] = [];
|
|
133
|
+
try {
|
|
134
|
+
for await (const chunk of representation.data) {
|
|
135
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
136
|
+
}
|
|
137
|
+
representation.data = guardedStreamFrom(Buffer.concat(chunks));
|
|
138
|
+
} catch {
|
|
139
|
+
// Stream already consumed, can't recover
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private shouldModerateContentType(contentType: string): boolean {
|
|
145
|
+
return contentType.startsWith('image/') ||
|
|
146
|
+
contentType.startsWith('video/') ||
|
|
147
|
+
contentType.startsWith('text/');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private async moderateImage(path: string, buffer: Buffer, contentType: string): Promise<void> {
|
|
151
|
+
const tempFile = `/tmp/moderation_${Date.now()}.${this.getFileExtension(contentType)}`;
|
|
152
|
+
writeFileSync(tempFile, buffer);
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
const result = await this.client.analyzeImage(tempFile);
|
|
156
|
+
|
|
157
|
+
if (result.nudity?.raw && result.nudity.raw > this.imageNudityThreshold) {
|
|
158
|
+
this.logger.warn(`Image BLOCKED: ${path} - nudity score ${result.nudity.raw.toFixed(2)} > ${this.imageNudityThreshold}`);
|
|
159
|
+
throw new ForbiddenHttpError(`Image contains inappropriate content (nudity: ${result.nudity.raw.toFixed(2)})`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
this.logger.info(`Image APPROVED: ${path}`);
|
|
163
|
+
} finally {
|
|
164
|
+
try {
|
|
165
|
+
unlinkSync(tempFile);
|
|
166
|
+
} catch {
|
|
167
|
+
this.logger.warn(`Failed to cleanup temp file: ${tempFile}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private async moderateVideo(path: string, buffer: Buffer, contentType: string): Promise<void> {
|
|
173
|
+
const tempFile = `/tmp/moderation_${Date.now()}.${this.getFileExtension(contentType)}`;
|
|
174
|
+
writeFileSync(tempFile, buffer);
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const result = await this.client.analyzeVideo(tempFile);
|
|
178
|
+
|
|
179
|
+
if (result.nudity?.raw && result.nudity.raw > this.videoNudityThreshold) {
|
|
180
|
+
this.logger.warn(`Video BLOCKED: ${path} - nudity score ${result.nudity.raw.toFixed(2)} > ${this.videoNudityThreshold}`);
|
|
181
|
+
throw new ForbiddenHttpError(`Video contains inappropriate content (nudity: ${result.nudity.raw.toFixed(2)})`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
this.logger.info(`Video APPROVED: ${path}`);
|
|
185
|
+
} finally {
|
|
186
|
+
try {
|
|
187
|
+
unlinkSync(tempFile);
|
|
188
|
+
} catch {
|
|
189
|
+
this.logger.warn(`Failed to cleanup temp file: ${tempFile}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private async moderateText(path: string, buffer: Buffer): Promise<void> {
|
|
195
|
+
const text = buffer.toString('utf-8');
|
|
196
|
+
const result = await this.client.analyzeText(text);
|
|
197
|
+
|
|
198
|
+
const violations: string[] = [];
|
|
199
|
+
|
|
200
|
+
if (result.sexual > this.textSexualThreshold) {
|
|
201
|
+
violations.push(`sexual content (${result.sexual.toFixed(2)} > ${this.textSexualThreshold})`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (result.toxic > this.textToxicThreshold) {
|
|
205
|
+
violations.push(`toxic content (${result.toxic.toFixed(2)} > ${this.textToxicThreshold})`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (violations.length > 0) {
|
|
209
|
+
this.logger.warn(`Text BLOCKED: ${path} - ${violations.join(', ')}`);
|
|
210
|
+
throw new ForbiddenHttpError(`Text contains inappropriate content: ${violations.join(', ')}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
this.logger.info(`Text APPROVED: ${path}`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private getFileExtension(contentType: string): string {
|
|
217
|
+
const map: Record<string, string> = {
|
|
218
|
+
'image/jpeg': 'jpg',
|
|
219
|
+
'image/png': 'png',
|
|
220
|
+
'image/gif': 'gif',
|
|
221
|
+
'image/webp': 'webp',
|
|
222
|
+
'video/mp4': 'mp4',
|
|
223
|
+
'video/webm': 'webm',
|
|
224
|
+
};
|
|
225
|
+
return map[contentType] || 'bin';
|
|
226
|
+
}
|
|
227
|
+
}
|