]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/helpers/custom-validators/activitypub/actor.ts
Merge branch 'release/3.1.0' into develop
[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 setValidDescription(actor) &&
66 // If this is a group (a channel), it should be attributed to an account
67 // In PeerTube we use this to attach a video channel to a specific account
68 (actor.type !== 'Group' || actor.attributedTo.length !== 0)
69 }
70
71 function isActorFollowingCountValid (value: string) {
72 return exists(value) && validator.isInt('' + value, { min: 0 })
73 }
74
75 function isActorFollowersCountValid (value: string) {
76 return exists(value) && validator.isInt('' + value, { min: 0 })
77 }
78
79 function isActorDeleteActivityValid (activity: any) {
80 return isBaseActivityValid(activity, 'Delete')
81 }
82
83 function sanitizeAndCheckActorObject (object: any) {
84 normalizeActor(object)
85
86 return isActorObjectValid(object)
87 }
88
89 function normalizeActor (actor: any) {
90 if (!actor) return
91
92 if (!actor.url) {
93 actor.url = actor.id
94 } else if (typeof actor.url !== 'string') {
95 actor.url = actor.url.href || actor.url.url
96 }
97
98 if (actor.summary && typeof actor.summary === 'string') {
99 actor.summary = peertubeTruncate(actor.summary, { length: CONSTRAINTS_FIELDS.USERS.DESCRIPTION.max })
100
101 if (actor.summary.length < CONSTRAINTS_FIELDS.USERS.DESCRIPTION.min) {
102 actor.summary = null
103 }
104 }
105 }
106
107 function isValidActorHandle (handle: string) {
108 if (!exists(handle)) return false
109
110 const parts = handle.split('@')
111 if (parts.length !== 2) return false
112
113 return isHostValid(parts[1])
114 }
115
116 function areValidActorHandles (handles: string[]) {
117 return isArray(handles) && handles.every(h => isValidActorHandle(h))
118 }
119
120 function setValidDescription (obj: any) {
121 if (!obj.summary) obj.summary = null
122
123 return true
124 }
125
126 // ---------------------------------------------------------------------------
127
128 export {
129 normalizeActor,
130 actorNameAlphabet,
131 areValidActorHandles,
132 isActorEndpointsObjectValid,
133 isActorPublicKeyObjectValid,
134 isActorTypeValid,
135 isActorPublicKeyValid,
136 isActorPreferredUsernameValid,
137 isActorPrivateKeyValid,
138 isActorObjectValid,
139 isActorFollowingCountValid,
140 isActorFollowersCountValid,
141 isActorDeleteActivityValid,
142 sanitizeAndCheckActorObject,
143 isValidActorHandle
144 }