]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/helpers/custom-validators/activitypub/actor.ts
emit more specific status codes on video upload (#3423)
[github/Chocobozzz/PeerTube.git] / server / helpers / custom-validators / activitypub / actor.ts
1 import validator from 'validator'
2 import { CONSTRAINTS_FIELDS } from '../../../initializers/constants'
3 import { exists, isArray } from '../misc'
4 import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc'
5 import { isHostValid } from '../servers'
6 import { peertubeTruncate } from '@server/helpers/core-utils'
7
8 function isActorEndpointsObjectValid (endpointObject: any) {
9 if (endpointObject?.sharedInbox) {
10 return isActivityPubUrlValid(endpointObject.sharedInbox)
11 }
12
13 // Shared inbox is optional
14 return true
15 }
16
17 function isActorPublicKeyObjectValid (publicKeyObject: any) {
18 return isActivityPubUrlValid(publicKeyObject.id) &&
19 isActivityPubUrlValid(publicKeyObject.owner) &&
20 isActorPublicKeyValid(publicKeyObject.publicKeyPem)
21 }
22
23 function isActorTypeValid (type: string) {
24 return type === 'Person' || type === 'Application' || type === 'Group' || type === 'Service' || type === 'Organization'
25 }
26
27 function isActorPublicKeyValid (publicKey: string) {
28 return exists(publicKey) &&
29 typeof publicKey === 'string' &&
30 publicKey.startsWith('-----BEGIN PUBLIC KEY-----') &&
31 publicKey.includes('-----END PUBLIC KEY-----') &&
32 validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY)
33 }
34
35 const actorNameAlphabet = '[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_.:]'
36 const actorNameRegExp = new RegExp(`^${actorNameAlphabet}+$`)
37 function isActorPreferredUsernameValid (preferredUsername: string) {
38 return exists(preferredUsername) && validator.matches(preferredUsername, actorNameRegExp)
39 }
40
41 function isActorPrivateKeyValid (privateKey: string) {
42 return exists(privateKey) &&
43 typeof privateKey === 'string' &&
44 privateKey.startsWith('-----BEGIN RSA PRIVATE KEY-----') &&
45 // Sometimes there is a \n at the end, so just assert the string contains the end mark
46 privateKey.includes('-----END RSA PRIVATE KEY-----') &&
47 validator.isLength(privateKey, CONSTRAINTS_FIELDS.ACTORS.PRIVATE_KEY)
48 }
49
50 function isActorObjectValid (actor: any) {
51 return exists(actor) &&
52 isActivityPubUrlValid(actor.id) &&
53 isActorTypeValid(actor.type) &&
54 isActivityPubUrlValid(actor.inbox) &&
55 isActorPreferredUsernameValid(actor.preferredUsername) &&
56 isActivityPubUrlValid(actor.url) &&
57 isActorPublicKeyObjectValid(actor.publicKey) &&
58 isActorEndpointsObjectValid(actor.endpoints) &&
59
60 (!actor.outbox || isActivityPubUrlValid(actor.outbox)) &&
61 (!actor.following || isActivityPubUrlValid(actor.following)) &&
62 (!actor.followers || isActivityPubUrlValid(actor.followers)) &&
63
64 setValidAttributedTo(actor) &&
65 // If this is a group (a channel), it should be attributed to an account
66 // In PeerTube we use this to attach a video channel to a specific account
67 (actor.type !== 'Group' || actor.attributedTo.length !== 0)
68 }
69
70 function isActorFollowingCountValid (value: string) {
71 return exists(value) && validator.isInt('' + value, { min: 0 })
72 }
73
74 function isActorFollowersCountValid (value: string) {
75 return exists(value) && validator.isInt('' + value, { min: 0 })
76 }
77
78 function isActorDeleteActivityValid (activity: any) {
79 return isBaseActivityValid(activity, 'Delete')
80 }
81
82 function sanitizeAndCheckActorObject (object: any) {
83 normalizeActor(object)
84
85 return isActorObjectValid(object)
86 }
87
88 function normalizeActor (actor: any) {
89 if (!actor) return
90
91 if (!actor.url) {
92 actor.url = actor.id
93 } else if (typeof actor.url !== 'string') {
94 actor.url = actor.url.href || actor.url.url
95 }
96
97 if (actor.summary && typeof actor.summary === 'string') {
98 actor.summary = peertubeTruncate(actor.summary, { length: CONSTRAINTS_FIELDS.USERS.DESCRIPTION.max })
99
100 if (actor.summary.length < CONSTRAINTS_FIELDS.USERS.DESCRIPTION.min) {
101 actor.summary = null
102 }
103 }
104 }
105
106 function isValidActorHandle (handle: string) {
107 if (!exists(handle)) return false
108
109 const parts = handle.split('@')
110 if (parts.length !== 2) return false
111
112 return isHostValid(parts[1])
113 }
114
115 function areValidActorHandles (handles: string[]) {
116 return isArray(handles) && handles.every(h => isValidActorHandle(h))
117 }
118
119 // ---------------------------------------------------------------------------
120
121 export {
122 normalizeActor,
123 actorNameAlphabet,
124 areValidActorHandles,
125 isActorEndpointsObjectValid,
126 isActorPublicKeyObjectValid,
127 isActorTypeValid,
128 isActorPublicKeyValid,
129 isActorPreferredUsernameValid,
130 isActorPrivateKeyValid,
131 isActorObjectValid,
132 isActorFollowingCountValid,
133 isActorFollowersCountValid,
134 isActorDeleteActivityValid,
135 sanitizeAndCheckActorObject,
136 isValidActorHandle
137 }