Merge branch 'feature/correctly-send-activities' into develop
[oweals/peertube.git] / server / helpers / custom-validators / activitypub / actor.ts
1 import * as validator from 'validator'
2 import { CONSTRAINTS_FIELDS } from '../../../initializers'
3 import { exists, isArray } from '../misc'
4 import { truncate } from 'lodash'
5 import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc'
6 import { isHostValid } from '../servers'
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.following) &&
50     isActivityPubUrlValid(actor.followers) &&
51     isActivityPubUrlValid(actor.inbox) &&
52     isActivityPubUrlValid(actor.outbox) &&
53     isActorPreferredUsernameValid(actor.preferredUsername) &&
54     isActivityPubUrlValid(actor.url) &&
55     isActorPublicKeyObjectValid(actor.publicKey) &&
56     isActorEndpointsObjectValid(actor.endpoints) &&
57     setValidAttributedTo(actor) &&
58
59     // If this is not an account, it should be attributed to an account
60     // In PeerTube we use this to attach a video channel to a specific account
61     (actor.type === 'Person' || actor.attributedTo.length !== 0)
62 }
63
64 function isActorFollowingCountValid (value: string) {
65   return exists(value) && validator.isInt('' + value, { min: 0 })
66 }
67
68 function isActorFollowersCountValid (value: string) {
69   return exists(value) && validator.isInt('' + value, { min: 0 })
70 }
71
72 function isActorDeleteActivityValid (activity: any) {
73   return isBaseActivityValid(activity, 'Delete')
74 }
75
76 function sanitizeAndCheckActorObject (object: any) {
77   normalizeActor(object)
78
79   return isActorObjectValid(object)
80 }
81
82 function normalizeActor (actor: any) {
83   if (!actor || !actor.url) return
84
85   if (typeof actor.url !== 'string') {
86     actor.url = actor.url.href || actor.url.url
87   }
88
89   if (actor.summary && typeof actor.summary === 'string') {
90     actor.summary = truncate(actor.summary, { length: CONSTRAINTS_FIELDS.USERS.DESCRIPTION.max })
91
92     if (actor.summary.length < CONSTRAINTS_FIELDS.USERS.DESCRIPTION.min) {
93       actor.summary = null
94     }
95   }
96
97   return
98 }
99
100 function isValidActorHandle (handle: string) {
101   if (!exists(handle)) return false
102
103   const parts = handle.split('@')
104   if (parts.length !== 2) return false
105
106   return isHostValid(parts[1])
107 }
108
109 function areValidActorHandles (handles: string[]) {
110   return isArray(handles) && handles.every(h => isValidActorHandle(h))
111 }
112
113 // ---------------------------------------------------------------------------
114
115 export {
116   normalizeActor,
117   actorNameAlphabet,
118   areValidActorHandles,
119   isActorEndpointsObjectValid,
120   isActorPublicKeyObjectValid,
121   isActorTypeValid,
122   isActorPublicKeyValid,
123   isActorPreferredUsernameValid,
124   isActorPrivateKeyValid,
125   isActorObjectValid,
126   isActorFollowingCountValid,
127   isActorFollowersCountValid,
128   isActorDeleteActivityValid,
129   sanitizeAndCheckActorObject,
130   isValidActorHandle
131 }