]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/helpers/custom-validators/activitypub/actor.ts
Merge branch 'release/2.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 && 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.indexOf('-----END PUBLIC KEY-----') !== -1 &&
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.indexOf('-----END RSA PRIVATE KEY-----') !== -1 &&
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 return
106 }
107
108 function isValidActorHandle (handle: string) {
109 if (!exists(handle)) return false
110
111 const parts = handle.split('@')
112 if (parts.length !== 2) return false
113
114 return isHostValid(parts[1])
115 }
116
117 function areValidActorHandles (handles: string[]) {
118 return isArray(handles) && handles.every(h => isValidActorHandle(h))
119 }
120
121 // ---------------------------------------------------------------------------
122
123 export {
124 normalizeActor,
125 actorNameAlphabet,
126 areValidActorHandles,
127 isActorEndpointsObjectValid,
128 isActorPublicKeyObjectValid,
129 isActorTypeValid,
130 isActorPublicKeyValid,
131 isActorPreferredUsernameValid,
132 isActorPrivateKeyValid,
133 isActorObjectValid,
134 isActorFollowingCountValid,
135 isActorFollowersCountValid,
136 isActorDeleteActivityValid,
137 sanitizeAndCheckActorObject,
138 isValidActorHandle
139 }