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