Status are sent to mastodon
This commit is contained in:
parent
ce33ee01cd
commit
e12a009254
|
@ -1,8 +1,8 @@
|
||||||
import * as validator from 'validator'
|
import * as validator from 'validator'
|
||||||
import { CONSTRAINTS_FIELDS } from '../../../initializers'
|
import { CONSTRAINTS_FIELDS } from '../../../initializers'
|
||||||
import { isAccountNameValid } from '../accounts'
|
import { isAccountNameValid } from '../accounts'
|
||||||
import { exists, isUUIDValid } from '../misc'
|
import { exists } from '../misc'
|
||||||
import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels'
|
import { isVideoChannelNameValid } from '../video-channels'
|
||||||
import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc'
|
import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc'
|
||||||
|
|
||||||
function isActorEndpointsObjectValid (endpointObject: any) {
|
function isActorEndpointsObjectValid (endpointObject: any) {
|
||||||
|
@ -23,41 +23,39 @@ function isActorPublicKeyValid (publicKey: string) {
|
||||||
return exists(publicKey) &&
|
return exists(publicKey) &&
|
||||||
typeof publicKey === 'string' &&
|
typeof publicKey === 'string' &&
|
||||||
publicKey.startsWith('-----BEGIN PUBLIC KEY-----') &&
|
publicKey.startsWith('-----BEGIN PUBLIC KEY-----') &&
|
||||||
publicKey.endsWith('-----END PUBLIC KEY-----') &&
|
publicKey.indexOf('-----END PUBLIC KEY-----') !== -1 &&
|
||||||
validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTOR.PUBLIC_KEY)
|
validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTOR.PUBLIC_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const actorNameRegExp = new RegExp('[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_]+')
|
||||||
function isActorPreferredUsernameValid (preferredUsername: string) {
|
function isActorPreferredUsernameValid (preferredUsername: string) {
|
||||||
return isAccountNameValid(preferredUsername) || isVideoChannelNameValid(preferredUsername)
|
return exists(preferredUsername) && validator.matches(preferredUsername, actorNameRegExp)
|
||||||
}
|
}
|
||||||
|
|
||||||
const actorNameRegExp = new RegExp('[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_]+')
|
|
||||||
function isActorNameValid (name: string) {
|
function isActorNameValid (name: string) {
|
||||||
return exists(name) && validator.matches(name, actorNameRegExp)
|
return isAccountNameValid(name) || isVideoChannelNameValid(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isActorPrivateKeyValid (privateKey: string) {
|
function isActorPrivateKeyValid (privateKey: string) {
|
||||||
return exists(privateKey) &&
|
return exists(privateKey) &&
|
||||||
typeof privateKey === 'string' &&
|
typeof privateKey === 'string' &&
|
||||||
privateKey.startsWith('-----BEGIN RSA PRIVATE KEY-----') &&
|
privateKey.startsWith('-----BEGIN RSA PRIVATE KEY-----') &&
|
||||||
privateKey.endsWith('-----END RSA PRIVATE KEY-----') &&
|
// Sometimes there is a \n at the end, so just assert the string contains the end mark
|
||||||
|
privateKey.indexOf('-----END RSA PRIVATE KEY-----') !== -1 &&
|
||||||
validator.isLength(privateKey, CONSTRAINTS_FIELDS.ACTOR.PRIVATE_KEY)
|
validator.isLength(privateKey, CONSTRAINTS_FIELDS.ACTOR.PRIVATE_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRemoteActorValid (remoteActor: any) {
|
function isRemoteActorValid (remoteActor: any) {
|
||||||
return isActivityPubUrlValid(remoteActor.id) &&
|
return isActivityPubUrlValid(remoteActor.id) &&
|
||||||
isUUIDValid(remoteActor.uuid) &&
|
|
||||||
isActorTypeValid(remoteActor.type) &&
|
isActorTypeValid(remoteActor.type) &&
|
||||||
isActivityPubUrlValid(remoteActor.following) &&
|
isActivityPubUrlValid(remoteActor.following) &&
|
||||||
isActivityPubUrlValid(remoteActor.followers) &&
|
isActivityPubUrlValid(remoteActor.followers) &&
|
||||||
isActivityPubUrlValid(remoteActor.inbox) &&
|
isActivityPubUrlValid(remoteActor.inbox) &&
|
||||||
isActivityPubUrlValid(remoteActor.outbox) &&
|
isActivityPubUrlValid(remoteActor.outbox) &&
|
||||||
isActorNameValid(remoteActor.name) &&
|
|
||||||
isActorPreferredUsernameValid(remoteActor.preferredUsername) &&
|
isActorPreferredUsernameValid(remoteActor.preferredUsername) &&
|
||||||
isActivityPubUrlValid(remoteActor.url) &&
|
isActivityPubUrlValid(remoteActor.url) &&
|
||||||
isActorPublicKeyObjectValid(remoteActor.publicKey) &&
|
isActorPublicKeyObjectValid(remoteActor.publicKey) &&
|
||||||
isActorEndpointsObjectValid(remoteActor.endpoints) &&
|
isActorEndpointsObjectValid(remoteActor.endpoints) &&
|
||||||
(!remoteActor.summary || isVideoChannelDescriptionValid(remoteActor.summary)) &&
|
|
||||||
setValidAttributedTo(remoteActor) &&
|
setValidAttributedTo(remoteActor) &&
|
||||||
// If this is not an account, it should be attributed to an account
|
// If this is not an account, it should be attributed to an account
|
||||||
// In PeerTube we use this to attach a video channel to a specific account
|
// In PeerTube we use this to attach a video channel to a specific account
|
||||||
|
|
|
@ -316,7 +316,7 @@ const CACHE = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ACCEPT_HEADERS = ACTIVITY_PUB.POTENTIAL_ACCEPT_HEADERS.concat('html', 'application/json')
|
const ACCEPT_HEADERS = [ 'html', 'application/json' ].concat(ACTIVITY_PUB.POTENTIAL_ACCEPT_HEADERS)
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ async function up (utils: {
|
||||||
id integer NOT NULL,
|
id integer NOT NULL,
|
||||||
type enum_actor_type NOT NULL,
|
type enum_actor_type NOT NULL,
|
||||||
uuid uuid NOT NULL,
|
uuid uuid NOT NULL,
|
||||||
name character varying(255) NOT NULL,
|
"preferredUsername" character varying(255) NOT NULL,
|
||||||
url character varying(2000) NOT NULL,
|
url character varying(2000) NOT NULL,
|
||||||
"publicKey" character varying(5000),
|
"publicKey" character varying(5000),
|
||||||
"privateKey" character varying(5000),
|
"privateKey" character varying(5000),
|
||||||
|
@ -50,7 +50,7 @@ async function up (utils: {
|
||||||
`ALTER SEQUENCE actor_id_seq OWNED BY actor.id`,
|
`ALTER SEQUENCE actor_id_seq OWNED BY actor.id`,
|
||||||
`ALTER TABLE ONLY actor ALTER COLUMN id SET DEFAULT nextval('actor_id_seq'::regclass)`,
|
`ALTER TABLE ONLY actor ALTER COLUMN id SET DEFAULT nextval('actor_id_seq'::regclass)`,
|
||||||
`ALTER TABLE ONLY actor ADD CONSTRAINT actor_pkey PRIMARY KEY (id);`,
|
`ALTER TABLE ONLY actor ADD CONSTRAINT actor_pkey PRIMARY KEY (id);`,
|
||||||
`CREATE UNIQUE INDEX actor_name_server_id ON actor USING btree (name, "serverId")`,
|
`CREATE UNIQUE INDEX actor_preferred_username_server_id ON actor USING btree ("preferredUsername", "serverId")`,
|
||||||
`ALTER TABLE ONLY actor
|
`ALTER TABLE ONLY actor
|
||||||
ADD CONSTRAINT "actor_avatarId_fkey" FOREIGN KEY ("avatarId") REFERENCES avatar(id) ON UPDATE CASCADE ON DELETE CASCADE`,
|
ADD CONSTRAINT "actor_avatarId_fkey" FOREIGN KEY ("avatarId") REFERENCES avatar(id) ON UPDATE CASCADE ON DELETE CASCADE`,
|
||||||
`ALTER TABLE ONLY actor
|
`ALTER TABLE ONLY actor
|
||||||
|
@ -68,7 +68,7 @@ async function up (utils: {
|
||||||
`
|
`
|
||||||
INSERT INTO "actor"
|
INSERT INTO "actor"
|
||||||
(
|
(
|
||||||
type, uuid, name, url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl",
|
type, uuid, "preferredUsername", url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl",
|
||||||
"sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt"
|
"sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt"
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
|
@ -83,7 +83,7 @@ async function up (utils: {
|
||||||
`
|
`
|
||||||
INSERT INTO "actor"
|
INSERT INTO "actor"
|
||||||
(
|
(
|
||||||
type, uuid, name, url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl",
|
type, uuid, "preferredUsername", url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl",
|
||||||
"sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt"
|
"sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt"
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
|
@ -119,7 +119,7 @@ async function up (utils: {
|
||||||
const query = `
|
const query = `
|
||||||
INSERT INTO actor
|
INSERT INTO actor
|
||||||
(
|
(
|
||||||
type, uuid, name, url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl",
|
type, uuid, "preferredUsername", url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl",
|
||||||
"sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt"
|
"sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt"
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
|
|
|
@ -107,7 +107,7 @@ function saveActorAndServerAndModelIfNotExist (
|
||||||
|
|
||||||
type FetchRemoteActorResult = {
|
type FetchRemoteActorResult = {
|
||||||
actor: ActorModel
|
actor: ActorModel
|
||||||
preferredUsername: string
|
name: string
|
||||||
summary: string
|
summary: string
|
||||||
attributedTo: ActivityPubAttributedTo[]
|
attributedTo: ActivityPubAttributedTo[]
|
||||||
}
|
}
|
||||||
|
@ -142,8 +142,8 @@ async function fetchRemoteActor (actorUrl: string): Promise<FetchRemoteActorResu
|
||||||
const actor = new ActorModel({
|
const actor = new ActorModel({
|
||||||
type: actorJSON.type,
|
type: actorJSON.type,
|
||||||
uuid: actorJSON.uuid,
|
uuid: actorJSON.uuid,
|
||||||
name: actorJSON.name,
|
preferredUsername: actorJSON.preferredUsername,
|
||||||
url: actorJSON.url,
|
url: actorJSON.id,
|
||||||
publicKey: actorJSON.publicKey.publicKeyPem,
|
publicKey: actorJSON.publicKey.publicKeyPem,
|
||||||
privateKey: null,
|
privateKey: null,
|
||||||
followersCount: followersCount,
|
followersCount: followersCount,
|
||||||
|
@ -155,19 +155,20 @@ async function fetchRemoteActor (actorUrl: string): Promise<FetchRemoteActorResu
|
||||||
followingUrl: actorJSON.following
|
followingUrl: actorJSON.following
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const name = actorJSON.name || actorJSON.preferredUsername
|
||||||
return {
|
return {
|
||||||
actor,
|
actor,
|
||||||
preferredUsername: actorJSON.preferredUsername,
|
name,
|
||||||
summary: actorJSON.summary,
|
summary: actorJSON.summary,
|
||||||
attributedTo: actorJSON.attributedTo
|
attributedTo: actorJSON.attributedTo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildActorInstance (type: ActivityPubActorType, url: string, name: string, uuid?: string) {
|
function buildActorInstance (type: ActivityPubActorType, url: string, preferredUsername: string, uuid?: string) {
|
||||||
return new ActorModel({
|
return new ActorModel({
|
||||||
type,
|
type,
|
||||||
url,
|
url,
|
||||||
name,
|
preferredUsername,
|
||||||
uuid,
|
uuid,
|
||||||
publicKey: null,
|
publicKey: null,
|
||||||
privateKey: null,
|
privateKey: null,
|
||||||
|
@ -210,7 +211,7 @@ async function fetchActorTotalItems (url: string) {
|
||||||
|
|
||||||
function saveAccount (actor: ActorModel, result: FetchRemoteActorResult, t: Transaction) {
|
function saveAccount (actor: ActorModel, result: FetchRemoteActorResult, t: Transaction) {
|
||||||
const account = new AccountModel({
|
const account = new AccountModel({
|
||||||
name: result.preferredUsername,
|
name: result.name,
|
||||||
actorId: actor.id
|
actorId: actor.id
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -219,7 +220,7 @@ function saveAccount (actor: ActorModel, result: FetchRemoteActorResult, t: Tran
|
||||||
|
|
||||||
async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResult, ownerActor: ActorModel, t: Transaction) {
|
async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResult, ownerActor: ActorModel, t: Transaction) {
|
||||||
const videoChannel = new VideoChannelModel({
|
const videoChannel = new VideoChannelModel({
|
||||||
name: result.preferredUsername,
|
name: result.name,
|
||||||
description: result.summary,
|
description: result.summary,
|
||||||
actorId: actor.id,
|
actorId: actor.id,
|
||||||
accountId: ownerActor.Account.id
|
accountId: ownerActor.Account.id
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Transaction } from 'sequelize'
|
import { Transaction } from 'sequelize'
|
||||||
import { Activity } from '../../../../shared/models/activitypub'
|
import { Activity, ActivityAudience } from '../../../../shared/models/activitypub'
|
||||||
import { logger } from '../../../helpers'
|
import { logger } from '../../../helpers'
|
||||||
import { ACTIVITY_PUB } from '../../../initializers'
|
import { ACTIVITY_PUB } from '../../../initializers'
|
||||||
import { ActorModel } from '../../../models/activitypub/actor'
|
import { ActorModel } from '../../../models/activitypub/actor'
|
||||||
|
@ -116,6 +116,10 @@ async function getAudience (actorSender: ActorModel, t: Transaction, isPublic =
|
||||||
return { to, cc }
|
return { to, cc }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function audiencify (object: any, audience: ActivityAudience) {
|
||||||
|
return Object.assign(object, audience)
|
||||||
|
}
|
||||||
|
|
||||||
async function computeFollowerUris (toActorFollower: ActorModel[], followersException: ActorModel[], t: Transaction) {
|
async function computeFollowerUris (toActorFollower: ActorModel[], followersException: ActorModel[], t: Transaction) {
|
||||||
const toActorFollowerIds = toActorFollower.map(a => a.id)
|
const toActorFollowerIds = toActorFollower.map(a => a.id)
|
||||||
|
|
||||||
|
@ -133,5 +137,6 @@ export {
|
||||||
getOriginVideoAudience,
|
getOriginVideoAudience,
|
||||||
getActorsInvolvedInVideo,
|
getActorsInvolvedInVideo,
|
||||||
getObjectFollowersAudience,
|
getObjectFollowersAudience,
|
||||||
forwardActivity
|
forwardActivity,
|
||||||
|
audiencify
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
import { Transaction } from 'sequelize'
|
import { Transaction } from 'sequelize'
|
||||||
import { ActivityAccept } from '../../../../shared/models/activitypub'
|
import { ActivityAccept, ActivityFollow } from '../../../../shared/models/activitypub'
|
||||||
import { ActorModel } from '../../../models/activitypub/actor'
|
import { ActorModel } from '../../../models/activitypub/actor'
|
||||||
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
|
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
|
||||||
import { getActorFollowAcceptActivityPubUrl } from '../url'
|
import { getActorFollowAcceptActivityPubUrl, getActorFollowActivityPubUrl } from '../url'
|
||||||
import { unicastTo } from './misc'
|
import { unicastTo } from './misc'
|
||||||
|
import { followActivityData } from './send-follow'
|
||||||
|
|
||||||
async function sendAccept (actorFollow: ActorFollowModel, t: Transaction) {
|
async function sendAccept (actorFollow: ActorFollowModel, t: Transaction) {
|
||||||
const follower = actorFollow.ActorFollower
|
const follower = actorFollow.ActorFollower
|
||||||
const me = actorFollow.ActorFollowing
|
const me = actorFollow.ActorFollowing
|
||||||
|
|
||||||
|
const followUrl = getActorFollowActivityPubUrl(actorFollow)
|
||||||
|
const followData = followActivityData(followUrl, follower, me)
|
||||||
|
|
||||||
const url = getActorFollowAcceptActivityPubUrl(actorFollow)
|
const url = getActorFollowAcceptActivityPubUrl(actorFollow)
|
||||||
const data = acceptActivityData(url, me)
|
const data = acceptActivityData(url, me, followData)
|
||||||
|
|
||||||
return unicastTo(data, me, follower.inboxUrl, t)
|
return unicastTo(data, me, follower.inboxUrl, t)
|
||||||
}
|
}
|
||||||
|
@ -23,10 +27,11 @@ export {
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function acceptActivityData (url: string, byActor: ActorModel): ActivityAccept {
|
function acceptActivityData (url: string, byActor: ActorModel, followActivityData: ActivityFollow): ActivityAccept {
|
||||||
return {
|
return {
|
||||||
type: 'Accept',
|
type: 'Accept',
|
||||||
id: url,
|
id: url,
|
||||||
actor: byActor.url
|
actor: byActor.url,
|
||||||
|
object: followActivityData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { VideoModel } from '../../../models/video/video'
|
||||||
import { VideoAbuseModel } from '../../../models/video/video-abuse'
|
import { VideoAbuseModel } from '../../../models/video/video-abuse'
|
||||||
import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url'
|
import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url'
|
||||||
import {
|
import {
|
||||||
|
audiencify,
|
||||||
broadcastToFollowers,
|
broadcastToFollowers,
|
||||||
getActorsInvolvedInVideo,
|
getActorsInvolvedInVideo,
|
||||||
getAudience,
|
getAudience,
|
||||||
|
@ -16,9 +17,11 @@ import {
|
||||||
} from './misc'
|
} from './misc'
|
||||||
|
|
||||||
async function sendCreateVideo (video: VideoModel, t: Transaction) {
|
async function sendCreateVideo (video: VideoModel, t: Transaction) {
|
||||||
const byActor = video.VideoChannel.Account.Actor
|
if (video.privacy === VideoPrivacy.PRIVATE) return
|
||||||
|
|
||||||
|
const byActor = video.VideoChannel.Account.Actor
|
||||||
const videoObject = video.toActivityPubObject()
|
const videoObject = video.toActivityPubObject()
|
||||||
|
|
||||||
const audience = await getAudience(byActor, t, video.privacy === VideoPrivacy.PUBLIC)
|
const audience = await getAudience(byActor, t, video.privacy === VideoPrivacy.PUBLIC)
|
||||||
const data = await createActivityData(video.url, byActor, videoObject, t, audience)
|
const data = await createActivityData(video.url, byActor, videoObject, t, audience)
|
||||||
|
|
||||||
|
@ -93,14 +96,12 @@ async function createActivityData (
|
||||||
audience = await getAudience(byActor, t)
|
audience = await getAudience(byActor, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return audiencify({
|
||||||
type: 'Create',
|
type: 'Create',
|
||||||
id: url,
|
id: url,
|
||||||
actor: byActor.url,
|
actor: byActor.url,
|
||||||
to: audience.to,
|
object: audiencify(object, audience)
|
||||||
cc: audience.cc,
|
}, audience)
|
||||||
object
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createDislikeActivityData (byActor: ActorModel, video: VideoModel) {
|
function createDislikeActivityData (byActor: ActorModel, video: VideoModel) {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { ActorModel } from '../../../models/activitypub/actor'
|
||||||
import { VideoModel } from '../../../models/video/video'
|
import { VideoModel } from '../../../models/video/video'
|
||||||
import { getVideoLikeActivityPubUrl } from '../url'
|
import { getVideoLikeActivityPubUrl } from '../url'
|
||||||
import {
|
import {
|
||||||
|
audiencify,
|
||||||
broadcastToFollowers,
|
broadcastToFollowers,
|
||||||
getActorsInvolvedInVideo,
|
getActorsInvolvedInVideo,
|
||||||
getAudience,
|
getAudience,
|
||||||
|
@ -44,14 +45,12 @@ async function likeActivityData (
|
||||||
audience = await getAudience(byActor, t)
|
audience = await getAudience(byActor, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return audiencify({
|
||||||
type: 'Like',
|
type: 'Like',
|
||||||
id: url,
|
id: url,
|
||||||
actor: byActor.url,
|
actor: byActor.url,
|
||||||
to: audience.to,
|
|
||||||
cc: audience.cc,
|
|
||||||
object: video.url
|
object: video.url
|
||||||
}
|
}, audience)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
|
||||||
import { VideoModel } from '../../../models/video/video'
|
import { VideoModel } from '../../../models/video/video'
|
||||||
import { getActorFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url'
|
import { getActorFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url'
|
||||||
import {
|
import {
|
||||||
|
audiencify,
|
||||||
broadcastToFollowers,
|
broadcastToFollowers,
|
||||||
getActorsInvolvedInVideo,
|
getActorsInvolvedInVideo,
|
||||||
getAudience,
|
getAudience,
|
||||||
|
@ -112,12 +113,10 @@ async function undoActivityData (
|
||||||
audience = await getAudience(byActor, t)
|
audience = await getAudience(byActor, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return audiencify({
|
||||||
type: 'Undo',
|
type: 'Undo',
|
||||||
id: url,
|
id: url,
|
||||||
actor: byActor.url,
|
actor: byActor.url,
|
||||||
to: audience.to,
|
|
||||||
cc: audience.cc,
|
|
||||||
object
|
object
|
||||||
}
|
}, audience)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { ActorModel } from '../../../models/activitypub/actor'
|
||||||
import { VideoModel } from '../../../models/video/video'
|
import { VideoModel } from '../../../models/video/video'
|
||||||
import { VideoShareModel } from '../../../models/video/video-share'
|
import { VideoShareModel } from '../../../models/video/video-share'
|
||||||
import { getUpdateActivityPubUrl } from '../url'
|
import { getUpdateActivityPubUrl } from '../url'
|
||||||
import { broadcastToFollowers, getAudience } from './misc'
|
import { audiencify, broadcastToFollowers, getAudience } from './misc'
|
||||||
|
|
||||||
async function sendUpdateVideo (video: VideoModel, t: Transaction) {
|
async function sendUpdateVideo (video: VideoModel, t: Transaction) {
|
||||||
const byActor = video.VideoChannel.Account.Actor
|
const byActor = video.VideoChannel.Account.Actor
|
||||||
|
@ -41,12 +41,10 @@ async function updateActivityData (
|
||||||
audience = await getAudience(byActor, t)
|
audience = await getAudience(byActor, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return audiencify({
|
||||||
type: 'Update',
|
type: 'Update',
|
||||||
id: url,
|
id: url,
|
||||||
actor: byActor.url,
|
actor: byActor.url,
|
||||||
to: audience.to,
|
object: audiencify(object, audience)
|
||||||
cc: audience.cc,
|
}, audience)
|
||||||
object
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import { Transaction } from 'sequelize'
|
import { Transaction } from 'sequelize'
|
||||||
|
import { VideoPrivacy } from '../../../shared/models/videos'
|
||||||
import { getServerActor } from '../../helpers'
|
import { getServerActor } from '../../helpers'
|
||||||
import { VideoModel } from '../../models/video/video'
|
import { VideoModel } from '../../models/video/video'
|
||||||
import { VideoShareModel } from '../../models/video/video-share'
|
import { VideoShareModel } from '../../models/video/video-share'
|
||||||
import { sendVideoAnnounceToFollowers } from './send'
|
import { sendVideoAnnounceToFollowers } from './send'
|
||||||
|
|
||||||
async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) {
|
async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) {
|
||||||
|
if (video.privacy === VideoPrivacy.PRIVATE) return
|
||||||
|
|
||||||
const serverActor = await getServerActor()
|
const serverActor = await getServerActor()
|
||||||
|
|
||||||
const serverShare = VideoShareModel.create({
|
const serverShare = VideoShareModel.create({
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
import { doRequest, logger } from '../../../helpers'
|
import { doRequest, logger } from '../../../helpers'
|
||||||
import { ActivityPubHttpPayload, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler'
|
import { ActivityPubHttpPayload, buildSignedRequestOptions, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler'
|
||||||
|
|
||||||
async function process (payload: ActivityPubHttpPayload, jobId: number) {
|
async function process (payload: ActivityPubHttpPayload, jobId: number) {
|
||||||
logger.info('Processing ActivityPub broadcast in job %d.', jobId)
|
logger.info('Processing ActivityPub broadcast in job %d.', jobId)
|
||||||
|
|
||||||
const body = await computeBody(payload)
|
const body = await computeBody(payload)
|
||||||
|
const httpSignatureOptions = await buildSignedRequestOptions(payload)
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
uri: '',
|
uri: '',
|
||||||
json: body
|
json: body,
|
||||||
|
httpSignature: httpSignatureOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const uri of payload.uris) {
|
for (const uri of payload.uris) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { JobCategory } from '../../../../shared'
|
import { JobCategory } from '../../../../shared'
|
||||||
import { buildSignedActivity, logger } from '../../../helpers'
|
import { buildSignedActivity, getServerActor, logger } from '../../../helpers'
|
||||||
import { ACTIVITY_PUB } from '../../../initializers'
|
import { ACTIVITY_PUB } from '../../../initializers'
|
||||||
import { ActorModel } from '../../../models/activitypub/actor'
|
import { ActorModel } from '../../../models/activitypub/actor'
|
||||||
import { JobHandler, JobScheduler } from '../job-scheduler'
|
import { JobHandler, JobScheduler } from '../job-scheduler'
|
||||||
|
@ -46,16 +46,36 @@ async function computeBody (payload: ActivityPubHttpPayload) {
|
||||||
|
|
||||||
if (payload.signatureActorId) {
|
if (payload.signatureActorId) {
|
||||||
const actorSignature = await ActorModel.load(payload.signatureActorId)
|
const actorSignature = await ActorModel.load(payload.signatureActorId)
|
||||||
if (!actorSignature) throw new Error('Unknown signature account id.')
|
if (!actorSignature) throw new Error('Unknown signature actor id.')
|
||||||
body = await buildSignedActivity(actorSignature, payload.body)
|
body = await buildSignedActivity(actorSignature, payload.body)
|
||||||
}
|
}
|
||||||
|
|
||||||
return body
|
return body
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function buildSignedRequestOptions (payload: ActivityPubHttpPayload) {
|
||||||
|
let actor: ActorModel
|
||||||
|
if (payload.signatureActorId) {
|
||||||
|
actor = await ActorModel.load(payload.signatureActorId)
|
||||||
|
if (!actor) throw new Error('Unknown signature actor id.')
|
||||||
|
} else {
|
||||||
|
// We need to sign the request, so use the server
|
||||||
|
actor = await getServerActor()
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyId = actor.getWebfingerUrl()
|
||||||
|
return {
|
||||||
|
algorithm: 'rsa-sha256',
|
||||||
|
authorizationHeaderName: 'Signature',
|
||||||
|
keyId,
|
||||||
|
key: actor.privateKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ActivityPubHttpPayload,
|
ActivityPubHttpPayload,
|
||||||
activitypubHttpJobScheduler,
|
activitypubHttpJobScheduler,
|
||||||
maybeRetryRequestLater,
|
maybeRetryRequestLater,
|
||||||
computeBody
|
computeBody,
|
||||||
|
buildSignedRequestOptions
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
import { doRequest, logger } from '../../../helpers'
|
import { doRequest, logger } from '../../../helpers'
|
||||||
import { ActivityPubHttpPayload, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler'
|
import { ActivityPubHttpPayload, buildSignedRequestOptions, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler'
|
||||||
|
|
||||||
async function process (payload: ActivityPubHttpPayload, jobId: number) {
|
async function process (payload: ActivityPubHttpPayload, jobId: number) {
|
||||||
logger.info('Processing ActivityPub unicast in job %d.', jobId)
|
logger.info('Processing ActivityPub unicast in job %d.', jobId)
|
||||||
|
|
||||||
const body = await computeBody(payload)
|
const body = await computeBody(payload)
|
||||||
|
const httpSignatureOptions = await buildSignedRequestOptions(payload)
|
||||||
|
|
||||||
const uri = payload.uris[0]
|
const uri = payload.uris[0]
|
||||||
const options = {
|
const options = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
uri,
|
uri,
|
||||||
json: body
|
json: body,
|
||||||
|
httpSignature: httpSignatureOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import * as Bluebird from 'bluebird'
|
import * as Bluebird from 'bluebird'
|
||||||
|
import { VideoPrivacy } from '../../../../shared/models/videos'
|
||||||
import { computeResolutionsToTranscode, logger } from '../../../helpers'
|
import { computeResolutionsToTranscode, logger } from '../../../helpers'
|
||||||
import { sequelizeTypescript } from '../../../initializers'
|
import { sequelizeTypescript } from '../../../initializers'
|
||||||
import { VideoModel } from '../../../models/video/video'
|
import { VideoModel } from '../../../models/video/video'
|
||||||
|
@ -35,9 +36,11 @@ async function onSuccess (jobId: number, video: VideoModel, jobScheduler: JobSch
|
||||||
// Video does not exist anymore
|
// Video does not exist anymore
|
||||||
if (!videoDatabase) return undefined
|
if (!videoDatabase) return undefined
|
||||||
|
|
||||||
|
if (video.privacy !== VideoPrivacy.PRIVATE) {
|
||||||
// Now we'll add the video's meta data to our followers
|
// Now we'll add the video's meta data to our followers
|
||||||
await sendCreateVideo(video, undefined)
|
await sendCreateVideo(video, undefined)
|
||||||
await shareVideoByServerAndChannel(video, undefined)
|
await shareVideoByServerAndChannel(video, undefined)
|
||||||
|
}
|
||||||
|
|
||||||
const originalFileHeight = await videoDatabase.getOriginalFileHeight()
|
const originalFileHeight = await videoDatabase.getOriginalFileHeight()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { VideoResolution } from '../../../../shared'
|
import { VideoResolution } from '../../../../shared'
|
||||||
|
import { VideoPrivacy } from '../../../../shared/models/videos'
|
||||||
import { logger } from '../../../helpers'
|
import { logger } from '../../../helpers'
|
||||||
import { VideoModel } from '../../../models/video/video'
|
import { VideoModel } from '../../../models/video/video'
|
||||||
import { sendUpdateVideo } from '../../activitypub/send'
|
import { sendUpdateVideo } from '../../activitypub/send'
|
||||||
|
@ -31,7 +32,9 @@ async function onSuccess (jobId: number, video: VideoModel) {
|
||||||
// Video does not exist anymore
|
// Video does not exist anymore
|
||||||
if (!videoDatabase) return undefined
|
if (!videoDatabase) return undefined
|
||||||
|
|
||||||
|
if (video.privacy !== VideoPrivacy.PRIVATE) {
|
||||||
await sendUpdateVideo(video, undefined)
|
await sendUpdateVideo(video, undefined)
|
||||||
|
}
|
||||||
|
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,13 @@ import { ActorModel } from '../models/activitypub/actor'
|
||||||
async function checkSignature (req: Request, res: Response, next: NextFunction) {
|
async function checkSignature (req: Request, res: Response, next: NextFunction) {
|
||||||
const signatureObject: ActivityPubSignature = req.body.signature
|
const signatureObject: ActivityPubSignature = req.body.signature
|
||||||
|
|
||||||
logger.debug('Checking signature of actor %s...', signatureObject.creator)
|
const [ creator ] = signatureObject.creator.split('#')
|
||||||
|
|
||||||
|
logger.debug('Checking signature of actor %s...', creator)
|
||||||
|
|
||||||
let actor: ActorModel
|
let actor: ActorModel
|
||||||
try {
|
try {
|
||||||
actor = await getOrCreateActorAndServerAndModel(signatureObject.creator)
|
actor = await getOrCreateActorAndServerAndModel(creator)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('Cannot create remote actor and check signature.', err)
|
logger.error('Cannot create remote actor and check signature.', err)
|
||||||
return res.sendStatus(403)
|
return res.sendStatus(403)
|
||||||
|
@ -32,6 +34,7 @@ async function checkSignature (req: Request, res: Response, next: NextFunction)
|
||||||
function executeIfActivityPub (fun: RequestHandler | RequestHandler[]) {
|
function executeIfActivityPub (fun: RequestHandler | RequestHandler[]) {
|
||||||
return (req: Request, res: Response, next: NextFunction) => {
|
return (req: Request, res: Response, next: NextFunction) => {
|
||||||
const accepted = req.accepts(ACCEPT_HEADERS)
|
const accepted = req.accepts(ACCEPT_HEADERS)
|
||||||
|
console.log(accepted)
|
||||||
if (accepted === false || ACTIVITY_PUB.POTENTIAL_ACCEPT_HEADERS.indexOf(accepted) === -1) {
|
if (accepted === false || ACTIVITY_PUB.POTENTIAL_ACCEPT_HEADERS.indexOf(accepted) === -1) {
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,32 +2,15 @@ import { values } from 'lodash'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import * as Sequelize from 'sequelize'
|
import * as Sequelize from 'sequelize'
|
||||||
import {
|
import {
|
||||||
AllowNull,
|
AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, DefaultScope, ForeignKey, HasMany, HasOne, Is, IsUUID, Model, Scopes,
|
||||||
BelongsTo,
|
Table, UpdatedAt
|
||||||
Column,
|
|
||||||
CreatedAt,
|
|
||||||
DataType,
|
|
||||||
Default, DefaultScope,
|
|
||||||
ForeignKey,
|
|
||||||
HasMany,
|
|
||||||
HasOne,
|
|
||||||
Is,
|
|
||||||
IsUUID,
|
|
||||||
Model,
|
|
||||||
Scopes,
|
|
||||||
Table,
|
|
||||||
UpdatedAt
|
|
||||||
} from 'sequelize-typescript'
|
} from 'sequelize-typescript'
|
||||||
import { ActivityPubActorType } from '../../../shared/models/activitypub'
|
import { ActivityPubActorType } from '../../../shared/models/activitypub'
|
||||||
import { Avatar } from '../../../shared/models/avatars/avatar.model'
|
import { Avatar } from '../../../shared/models/avatars/avatar.model'
|
||||||
import { activityPubContextify } from '../../helpers'
|
import { activityPubContextify } from '../../helpers'
|
||||||
import {
|
import {
|
||||||
isActivityPubUrlValid,
|
isActivityPubUrlValid, isActorFollowersCountValid, isActorFollowingCountValid, isActorPreferredUsernameValid,
|
||||||
isActorFollowersCountValid,
|
isActorPrivateKeyValid, isActorPublicKeyValid
|
||||||
isActorFollowingCountValid,
|
|
||||||
isActorNameValid,
|
|
||||||
isActorPrivateKeyValid,
|
|
||||||
isActorPublicKeyValid
|
|
||||||
} from '../../helpers/custom-validators/activitypub'
|
} from '../../helpers/custom-validators/activitypub'
|
||||||
import { ACTIVITY_PUB_ACTOR_TYPES, AVATARS_DIR, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers'
|
import { ACTIVITY_PUB_ACTOR_TYPES, AVATARS_DIR, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers'
|
||||||
import { AccountModel } from '../account/account'
|
import { AccountModel } from '../account/account'
|
||||||
|
@ -71,7 +54,7 @@ enum ScopeNames {
|
||||||
tableName: 'actor',
|
tableName: 'actor',
|
||||||
indexes: [
|
indexes: [
|
||||||
{
|
{
|
||||||
fields: [ 'name', 'serverId' ],
|
fields: [ 'preferredUsername', 'serverId' ],
|
||||||
unique: true
|
unique: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -89,9 +72,9 @@ export class ActorModel extends Model<ActorModel> {
|
||||||
uuid: string
|
uuid: string
|
||||||
|
|
||||||
@AllowNull(false)
|
@AllowNull(false)
|
||||||
@Is('ActorName', value => throwIfNotValid(value, isActorNameValid, 'actor name'))
|
@Is('ActorPreferredUsername', value => throwIfNotValid(value, isActorPreferredUsernameValid, 'actor preferred username'))
|
||||||
@Column
|
@Column
|
||||||
name: string
|
preferredUsername: string
|
||||||
|
|
||||||
@AllowNull(false)
|
@AllowNull(false)
|
||||||
@Is('ActorUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
|
@Is('ActorUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
|
||||||
|
@ -212,16 +195,6 @@ export class ActorModel extends Model<ActorModel> {
|
||||||
return ActorModel.scope(ScopeNames.FULL).findById(id)
|
return ActorModel.scope(ScopeNames.FULL).findById(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
static loadByUUID (uuid: string) {
|
|
||||||
const query = {
|
|
||||||
where: {
|
|
||||||
uuid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ActorModel.scope(ScopeNames.FULL).findOne(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) {
|
static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) {
|
||||||
const query = {
|
const query = {
|
||||||
where: {
|
where: {
|
||||||
|
@ -235,10 +208,10 @@ export class ActorModel extends Model<ActorModel> {
|
||||||
return ActorModel.scope(ScopeNames.FULL).findAll(query)
|
return ActorModel.scope(ScopeNames.FULL).findAll(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
static loadLocalByName (name: string) {
|
static loadLocalByName (preferredUsername: string) {
|
||||||
const query = {
|
const query = {
|
||||||
where: {
|
where: {
|
||||||
name,
|
preferredUsername,
|
||||||
serverId: null
|
serverId: null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -246,10 +219,10 @@ export class ActorModel extends Model<ActorModel> {
|
||||||
return ActorModel.scope(ScopeNames.FULL).findOne(query)
|
return ActorModel.scope(ScopeNames.FULL).findOne(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
static loadByNameAndHost (name: string, host: string) {
|
static loadByNameAndHost (preferredUsername: string, host: string) {
|
||||||
const query = {
|
const query = {
|
||||||
where: {
|
where: {
|
||||||
name
|
preferredUsername
|
||||||
},
|
},
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
|
@ -286,17 +259,15 @@ export class ActorModel extends Model<ActorModel> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let host = CONFIG.WEBSERVER.HOST
|
|
||||||
let score: number
|
let score: number
|
||||||
if (this.Server) {
|
if (this.Server) {
|
||||||
host = this.Server.host
|
|
||||||
score = this.Server.score
|
score = this.Server.score
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
uuid: this.uuid,
|
uuid: this.uuid,
|
||||||
host,
|
host: this.getHost(),
|
||||||
score,
|
score,
|
||||||
followingCount: this.followingCount,
|
followingCount: this.followingCount,
|
||||||
followersCount: this.followersCount,
|
followersCount: this.followersCount,
|
||||||
|
@ -304,7 +275,7 @@ export class ActorModel extends Model<ActorModel> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toActivityPubObject (preferredUsername: string, type: 'Account' | 'Application' | 'VideoChannel') {
|
toActivityPubObject (name: string, type: 'Account' | 'Application' | 'VideoChannel') {
|
||||||
let activityPubType
|
let activityPubType
|
||||||
if (type === 'Account') {
|
if (type === 'Account') {
|
||||||
activityPubType = 'Person' as 'Person'
|
activityPubType = 'Person' as 'Person'
|
||||||
|
@ -321,9 +292,9 @@ export class ActorModel extends Model<ActorModel> {
|
||||||
followers: this.getFollowersUrl(),
|
followers: this.getFollowersUrl(),
|
||||||
inbox: this.inboxUrl,
|
inbox: this.inboxUrl,
|
||||||
outbox: this.outboxUrl,
|
outbox: this.outboxUrl,
|
||||||
preferredUsername,
|
preferredUsername: this.preferredUsername,
|
||||||
url: this.url,
|
url: this.url,
|
||||||
name: this.name,
|
name,
|
||||||
endpoints: {
|
endpoints: {
|
||||||
sharedInbox: this.sharedInboxUrl
|
sharedInbox: this.sharedInboxUrl
|
||||||
},
|
},
|
||||||
|
@ -373,4 +344,12 @@ export class ActorModel extends Model<ActorModel> {
|
||||||
isOwned () {
|
isOwned () {
|
||||||
return this.serverId === null
|
return this.serverId === null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getWebfingerUrl () {
|
||||||
|
return 'acct:' + this.preferredUsername + '@' + this.getHost()
|
||||||
|
}
|
||||||
|
|
||||||
|
getHost () {
|
||||||
|
return this.Server ? this.Server.host : CONFIG.WEBSERVER.HOST
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,7 @@ export interface ActivityFollow extends BaseActivity {
|
||||||
|
|
||||||
export interface ActivityAccept extends BaseActivity {
|
export interface ActivityAccept extends BaseActivity {
|
||||||
type: 'Accept'
|
type: 'Accept'
|
||||||
|
object: ActivityFollow
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ActivityAnnounce extends BaseActivity {
|
export interface ActivityAnnounce extends BaseActivity {
|
||||||
|
|
|
@ -2378,9 +2378,9 @@ jsonify@~0.0.0:
|
||||||
version "0.0.0"
|
version "0.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
|
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
|
||||||
|
|
||||||
"jsonld-signatures@https://github.com/digitalbazaar/jsonld-signatures#rsa2017":
|
"jsonld-signatures@https://github.com/Chocobozzz/jsonld-signatures#rsa2017":
|
||||||
version "1.2.2-2"
|
version "1.2.2-2"
|
||||||
resolved "https://github.com/digitalbazaar/jsonld-signatures#ccb5ca156d72d7632131080d6ef564681791391e"
|
resolved "https://github.com/Chocobozzz/jsonld-signatures#77660963e722eb4541d2d255f9d9d4216329665f"
|
||||||
dependencies:
|
dependencies:
|
||||||
bitcore-message "github:CoMakery/bitcore-message#dist"
|
bitcore-message "github:CoMakery/bitcore-message#dist"
|
||||||
jsonld "^0.5.12"
|
jsonld "^0.5.12"
|
||||||
|
|
Loading…
Reference in New Issue