Update dependencies
[oweals/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 }