New sticker creator button
This commit is contained in:
parent
85adb39d31
commit
fad0529080
25 changed files with 442 additions and 11 deletions
|
@ -34,6 +34,7 @@ const JITTER = 5 * durations.SECOND;
|
|||
|
||||
export type SocketManagerOptions = Readonly<{
|
||||
url: string;
|
||||
artCreatorUrl: string;
|
||||
certificateAuthority: string;
|
||||
version: string;
|
||||
proxyUrl?: string;
|
||||
|
@ -276,6 +277,27 @@ export class SocketManager extends EventListener {
|
|||
}).getResult();
|
||||
}
|
||||
|
||||
// Creates new WebSocket for Art Creator provisioning
|
||||
public async connectExternalSocket({
|
||||
url,
|
||||
extraHeaders,
|
||||
}: {
|
||||
url: string;
|
||||
extraHeaders?: Record<string, string>;
|
||||
}): Promise<WebSocket> {
|
||||
return connectWebSocket({
|
||||
name: 'art-creator-provisioning',
|
||||
url,
|
||||
version: this.options.version,
|
||||
proxyAgent: this.proxyAgent,
|
||||
extraHeaders,
|
||||
|
||||
createResource(socket: WebSocket): WebSocket {
|
||||
return socket;
|
||||
},
|
||||
}).getResult();
|
||||
}
|
||||
|
||||
// Fetch-compatible wrapper around underlying unauthenticated/authenticated
|
||||
// websocket resources. This wrapper supports only limited number of features
|
||||
// of node-fetch despite being API compatible.
|
||||
|
|
|
@ -15,6 +15,7 @@ import PQueue from 'p-queue';
|
|||
import { v4 as getGuid } from 'uuid';
|
||||
import { z } from 'zod';
|
||||
import type { Readable } from 'stream';
|
||||
import type { connection as WebSocket } from 'websocket';
|
||||
|
||||
import { assertDev, strictAssert } from '../util/assert';
|
||||
import { isRecord } from '../util/isRecord';
|
||||
|
@ -477,7 +478,6 @@ const URL_CALLS = {
|
|||
config: 'v1/config',
|
||||
deliveryCert: 'v1/certificate/delivery',
|
||||
devices: 'v1/devices',
|
||||
directoryAuth: 'v1/directory/auth',
|
||||
directoryAuthV2: 'v2/directory/auth',
|
||||
discovery: 'v1/discovery',
|
||||
getGroupAvatarUpload: 'v1/groups/avatar/form',
|
||||
|
@ -486,6 +486,7 @@ const URL_CALLS = {
|
|||
getOnboardingStoryManifest:
|
||||
'dynamic/desktop/stories/onboarding/manifest.json',
|
||||
getStickerPackUpload: 'v1/sticker/pack/form',
|
||||
getArtAuth: 'v1/art/auth',
|
||||
groupLog: 'v1/groups/logs',
|
||||
groupJoinedAtVersion: 'v1/groups/joined_at_version',
|
||||
groups: 'v1/groups',
|
||||
|
@ -537,7 +538,6 @@ const WEBSOCKET_CALLS = new Set<keyof typeof URL_CALLS>([
|
|||
'supportUnauthenticatedDelivery',
|
||||
|
||||
// Directory
|
||||
'directoryAuth',
|
||||
'directoryAuthV2',
|
||||
|
||||
// Storage
|
||||
|
@ -552,6 +552,7 @@ type InitializeOptionsType = {
|
|||
storageUrl: string;
|
||||
updatesUrl: string;
|
||||
resourcesUrl: string;
|
||||
artCreatorUrl: string;
|
||||
cdnUrlObject: {
|
||||
readonly '0': string;
|
||||
readonly [propName: string]: string;
|
||||
|
@ -831,6 +832,13 @@ export type ReportMessageOptionsType = Readonly<{
|
|||
token?: string;
|
||||
}>;
|
||||
|
||||
const artAuthZod = z.object({
|
||||
username: z.string(),
|
||||
password: z.string(),
|
||||
});
|
||||
|
||||
export type ArtAuthType = z.infer<typeof artAuthZod>;
|
||||
|
||||
export type WebAPIType = {
|
||||
startRegistration(): unknown;
|
||||
finishRegistration(baton: unknown): void;
|
||||
|
@ -848,6 +856,7 @@ export type WebAPIType = {
|
|||
version: string,
|
||||
imageFiles: Array<string>
|
||||
) => Promise<Array<Uint8Array>>;
|
||||
getArtAuth: () => Promise<ArtAuthType>;
|
||||
getAttachment: (cdnKey: string, cdnNumber?: number) => Promise<Uint8Array>;
|
||||
getAvatar: (path: string) => Promise<Uint8Array>;
|
||||
getDevices: () => Promise<GetDevicesResultType>;
|
||||
|
@ -901,6 +910,7 @@ export type WebAPIType = {
|
|||
getProvisioningResource: (
|
||||
handler: IRequestHandler
|
||||
) => Promise<WebSocketResource>;
|
||||
getArtProvisioningSocket: (token: string) => Promise<WebSocket>;
|
||||
getSenderCertificate: (
|
||||
withUuid?: boolean
|
||||
) => Promise<GetSenderCertificateResultType>;
|
||||
|
@ -1073,6 +1083,7 @@ export function initialize({
|
|||
storageUrl,
|
||||
updatesUrl,
|
||||
resourcesUrl,
|
||||
artCreatorUrl,
|
||||
directoryConfig,
|
||||
cdnUrlObject,
|
||||
certificateAuthority,
|
||||
|
@ -1092,6 +1103,9 @@ export function initialize({
|
|||
if (!isString(resourcesUrl)) {
|
||||
throw new Error('WebAPI.initialize: Invalid updatesUrl (general)');
|
||||
}
|
||||
if (!isString(artCreatorUrl)) {
|
||||
throw new Error('WebAPI.initialize: Invalid artCreatorUrl');
|
||||
}
|
||||
if (!isObject(cdnUrlObject)) {
|
||||
throw new Error('WebAPI.initialize: Invalid cdnUrlObject');
|
||||
}
|
||||
|
@ -1139,6 +1153,7 @@ export function initialize({
|
|||
|
||||
const socketManager = new SocketManager({
|
||||
url,
|
||||
artCreatorUrl,
|
||||
certificateAuthority,
|
||||
version,
|
||||
proxyUrl,
|
||||
|
@ -1224,6 +1239,8 @@ export function initialize({
|
|||
fetchLinkPreviewMetadata,
|
||||
finishRegistration,
|
||||
getAccountForUsername,
|
||||
getArtAuth,
|
||||
getArtProvisioningSocket,
|
||||
getAttachment,
|
||||
getAvatar,
|
||||
getBadgeImageFile,
|
||||
|
@ -2982,6 +2999,15 @@ export function initialize({
|
|||
return socketManager.getProvisioningResource(handler);
|
||||
}
|
||||
|
||||
function getArtProvisioningSocket(token: string): Promise<WebSocket> {
|
||||
return socketManager.connectExternalSocket({
|
||||
url: `${artCreatorUrl}/api/socket?token=${token}`,
|
||||
extraHeaders: {
|
||||
origin: artCreatorUrl,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function cdsLookup({
|
||||
e164s,
|
||||
acis = [],
|
||||
|
@ -2995,5 +3021,19 @@ export function initialize({
|
|||
returnAcisWithoutUaks,
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Art
|
||||
//
|
||||
|
||||
async function getArtAuth(): Promise<ArtAuthType> {
|
||||
const response = await _ajax({
|
||||
call: 'getArtAuth',
|
||||
httpType: 'GET',
|
||||
responseType: 'json',
|
||||
});
|
||||
|
||||
return artAuthZod.parse(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ export type IResource = {
|
|||
export type ConnectOptionsType<Resource extends IResource> = Readonly<{
|
||||
name: string;
|
||||
url: string;
|
||||
certificateAuthority: string;
|
||||
certificateAuthority?: string;
|
||||
version: string;
|
||||
proxyAgent?: ReturnType<typeof ProxyAgent>;
|
||||
timeout?: number;
|
||||
|
|
59
ts/textsecure/authorizeArtCreator.ts
Normal file
59
ts/textsecure/authorizeArtCreator.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { promisify } from 'util';
|
||||
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
import { calculateAgreement, generateKeyPair } from '../Curve';
|
||||
import { encryptAttachment, deriveSecrets } from '../Crypto';
|
||||
import * as Bytes from '../Bytes';
|
||||
|
||||
const PROVISIONING_INFO = 'Art Service Provisioning Message';
|
||||
|
||||
export type AuthorizeArtCreatorOptionsType = Readonly<{
|
||||
token: string;
|
||||
pubKeyBase64: string;
|
||||
}>;
|
||||
|
||||
export async function authorizeArtCreator({
|
||||
token,
|
||||
pubKeyBase64: theirPubKeyBase64,
|
||||
}: AuthorizeArtCreatorOptionsType): Promise<void> {
|
||||
const { server } = window.textsecure;
|
||||
if (!server) {
|
||||
throw new Error('Server not ready');
|
||||
}
|
||||
|
||||
const auth = await server.getArtAuth();
|
||||
|
||||
const ourKeys = generateKeyPair();
|
||||
const theirPubKey = Bytes.fromBase64(theirPubKeyBase64);
|
||||
|
||||
const secret = calculateAgreement(theirPubKey, ourKeys.privKey);
|
||||
const [aesKey, macKey] = deriveSecrets(
|
||||
secret,
|
||||
new Uint8Array(64),
|
||||
Bytes.fromString(PROVISIONING_INFO)
|
||||
);
|
||||
const keys = Bytes.concatenate([aesKey, macKey]);
|
||||
|
||||
const { ciphertext } = encryptAttachment(
|
||||
Proto.ArtProvisioningMessage.encode({
|
||||
...auth,
|
||||
}).finish(),
|
||||
keys
|
||||
);
|
||||
|
||||
const envelope = Proto.ArtProvisioningEnvelope.encode({
|
||||
publicKey: ourKeys.pubKey,
|
||||
ciphertext,
|
||||
}).finish();
|
||||
|
||||
const socket = await server.getArtProvisioningSocket(token);
|
||||
|
||||
try {
|
||||
await promisify(socket.sendBytes).call(socket, Buffer.from(envelope));
|
||||
} finally {
|
||||
socket.close(1000, 'goodbye');
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue