From 31cbb89b0d2f23b20b19291c70bf39e4e86a9fce Mon Sep 17 00:00:00 2001 From: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com> Date: Wed, 15 May 2024 15:26:37 -0700 Subject: [PATCH] Move sticker creator API to chat service --- _locales/en/messages.json | 6 +- app/main.ts | 59 +++----- config/default.json | 1 - config/production.json | 1 - sticker-creator/package.json | 1 - sticker-creator/src/types.d.ts | 1 - sticker-creator/src/util/api.ts | 181 +++--------------------- sticker-creator/yarn.lock | 14 +- ts/components/GlobalModalContainer.tsx | 34 ----- ts/state/ducks/globalModals.ts | 137 ------------------ ts/state/smart/GlobalModalContainer.tsx | 8 -- ts/test-node/util/signalRoutes_test.ts | 10 -- ts/textsecure/SocketManager.ts | 1 - ts/textsecure/WebAPI.ts | 74 ++++------ ts/textsecure/authorizeArtCreator.ts | 59 -------- ts/types/RendererConfig.ts | 1 - ts/util/createIPCEvents.ts | 34 ++--- ts/util/signalRoutes.ts | 33 ----- ts/windows/main/phase1-ipc.ts | 33 ++--- ts/windows/main/phase2-dependencies.ts | 1 - ts/windows/sticker-creator/preload.ts | 22 ++- 21 files changed, 124 insertions(+), 587 deletions(-) delete mode 100644 ts/textsecure/authorizeArtCreator.ts diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 2959e85f9b9..15e890ae376 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -3148,15 +3148,15 @@ }, "icu:AuthArtCreator--dialog--message": { "messageformat": "Would you like to open Signal Sticker Pack Creator?", - "description": "A body of the dialog that is presented when user tries to open Signal Sticker Pack Creator from a link" + "description": "(Deleted 2024/05/15) A body of the dialog that is presented when user tries to open Signal Sticker Pack Creator from a link" }, "icu:AuthArtCreator--dialog--confirm": { "messageformat": "Confirm", - "description": "A buttle title for confirming Signal Sticker Pack Creator dialog" + "description": "(Deleted 2024/05/15) A buttle title for confirming Signal Sticker Pack Creator dialog" }, "icu:AuthArtCreator--dialog--dismiss": { "messageformat": "Dismiss", - "description": "A buttle title for dismissing Signal Sticker Pack Creator dialog" + "description": "(Deleted 2024/05/15) A buttle title for dismissing Signal Sticker Pack Creator dialog" }, "icu:ArtCreator--Authentication--error": { "messageformat": "Please set up Signal on your phone and desktop to use the Sticker Pack Creator", diff --git a/app/main.ts b/app/main.ts index e04aae77d3f..ff7632e1bbc 100644 --- a/app/main.ts +++ b/app/main.ts @@ -120,8 +120,6 @@ import { parseSignalRoute } from '../ts/util/signalRoutes'; import * as dns from '../ts/util/dns'; import { ZoomFactorService } from '../ts/services/ZoomFactorService'; -const STICKER_CREATOR_PARTITION = 'sticker-creator'; - const animationSettings = systemPreferences.getAnimationSettings(); if (OS.isMacOS()) { @@ -1000,21 +998,24 @@ ipc.handle('database-ready', async () => { getLogger().info('sending `database-ready`'); }); -ipc.handle('get-art-creator-auth', () => { - const { promise, resolve } = explodePromise(); - strictAssert(mainWindow, 'Main window did not exist'); +ipc.handle( + 'art-creator:uploadStickerPack', + (_event: Electron.Event, data: unknown) => { + const { promise, resolve } = explodePromise(); + strictAssert(mainWindow, 'Main window did not exist'); - mainWindow.webContents.send('open-art-creator'); + mainWindow.webContents.send('art-creator:uploadStickerPack', data); - ipc.handleOnce('open-art-creator', (_event, { username, password }) => { - resolve({ - baseUrl: config.get('artCreatorUrl'), - username, - password, + ipc.once('art-creator:uploadStickerPack:done', (_doneEvent, response) => { + resolve(response); }); - }); - return promise; + return promise; + } +); + +ipc.on('art-creator:onUploadProgress', () => { + stickerCreatorWindow?.webContents.send('art-creator:onUploadProgress'); }); ipc.on('show-window', () => { @@ -1735,19 +1736,15 @@ app.on('ready', async () => { realpath(app.getAppPath()), ]); - const webSession = session.fromPartition(STICKER_CREATOR_PARTITION); + updateDefaultSession(session.defaultSession); - for (const s of [session.defaultSession, webSession]) { - updateDefaultSession(s); - - if (getEnvironment() !== Environment.Test) { - installFileHandler({ - session: s, - userDataPath, - installPath, - isWindows: OS.isWindows(), - }); - } + if (getEnvironment() !== Environment.Test) { + installFileHandler({ + session: session.defaultSession, + userDataPath, + installPath, + isWindows: OS.isWindows(), + }); } installWebHandler({ @@ -1755,11 +1752,6 @@ app.on('ready', async () => { session: session.defaultSession, }); - installWebHandler({ - enableHttp: true, - session: webSession, - }); - logger = await logging.initialize(getMainWindow); // Write buffered information into newly created logger. @@ -2454,7 +2446,6 @@ ipc.on('get-config', async event => { storageUrl: config.get('storageUrl'), updatesUrl: config.get('updatesUrl'), resourcesUrl: config.get('resourcesUrl'), - artCreatorUrl: config.get('artCreatorUrl'), cdnUrl0: config.get('cdn.0'), cdnUrl2: config.get('cdn.2'), cdnUrl3: config.get('cdn.3'), @@ -2605,11 +2596,6 @@ function handleSignalRoute(route: ParsedSignalRoute) { packId: route.args.packId, packKey: Buffer.from(route.args.packKey, 'hex').toString('base64'), }); - } else if (route.key === 'artAuth') { - mainWindow.webContents.send('authorize-art-creator', { - token: route.args.token, - pubKeyBase64: route.args.pubKey, - }); } else if (route.key === 'groupInvites') { mainWindow.webContents.send('show-group-via-link', { value: route.args.inviteCode, @@ -2895,7 +2881,6 @@ async function showStickerCreatorWindow() { show: false, webPreferences: { ...defaultWebPrefs, - partition: STICKER_CREATOR_PARTITION, nodeIntegration: false, nodeIntegrationInWorker: false, sandbox: true, diff --git a/config/default.json b/config/default.json index 2955dbaea6c..1bd9d3c7c97 100644 --- a/config/default.json +++ b/config/default.json @@ -11,7 +11,6 @@ "contentProxyUrl": "http://contentproxy.signal.org:443", "updatesUrl": "https://updates2.signal.org/desktop", "resourcesUrl": "https://updates2.signal.org", - "artCreatorUrl": "https://create.staging.signal.art", "updatesPublicKey": "05fd7dd3de7149dc0a127909fee7de0f7620ddd0de061b37a2c303e37de802a401", "sfuUrl": "https://sfu.staging.voip.signal.org/", "challengeUrl": "https://signalcaptchas.org/staging/challenge/generate.html", diff --git a/config/production.json b/config/production.json index fb3c848363d..5b9cae3ed38 100644 --- a/config/production.json +++ b/config/production.json @@ -8,7 +8,6 @@ "2": "https://cdn2.signal.org", "3": "https://cdn3.signal.org" }, - "artCreatorUrl": "https://create.signal.art", "sfuUrl": "https://sfu.voip.signal.org/", "challengeUrl": "https://signalcaptchas.org/challenge/generate.html", "registrationChallengeUrl": "https://signalcaptchas.org/registration/generate.html", diff --git a/sticker-creator/package.json b/sticker-creator/package.json index 79e8c284979..48ec19b61f8 100644 --- a/sticker-creator/package.json +++ b/sticker-creator/package.json @@ -34,7 +34,6 @@ "focus-trap-react": "10.1.1", "memoizee": "0.4.15", "npm-run-all": "4.1.5", - "p-limit": "4.0.0", "protobufjs": "7.2.5", "protobufjs-cli": "1.1.1", "qrcode-generator": "1.4.4", diff --git a/sticker-creator/src/types.d.ts b/sticker-creator/src/types.d.ts index a1325dea398..5fd08a01f88 100644 --- a/sticker-creator/src/types.d.ts +++ b/sticker-creator/src/types.d.ts @@ -6,7 +6,6 @@ // export type Credentials = Readonly<{ - baseUrl?: string; username: string; password: string; }>; diff --git a/sticker-creator/src/util/api.ts b/sticker-creator/src/util/api.ts index c6ebcc803b6..d4a732c1189 100644 --- a/sticker-creator/src/util/api.ts +++ b/sticker-creator/src/util/api.ts @@ -1,21 +1,17 @@ // Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import b64 from 'base64-js'; -import pLimit from 'p-limit'; -import type { infer as zInfer } from 'zod'; -import z from 'zod'; - import { type ArtType } from '../constants'; -import { type Credentials } from '../types.d'; -import { type EncryptResult, getRandomString } from './crypto'; - -const MAX_PARALLEL_UPLOADS = 10; +import { type EncryptResult } from './crypto'; declare global { // eslint-disable-next-line no-restricted-syntax interface Window { - getCredentials(): Promise; + uploadStickerPack( + manifest: Uint8Array, + stickers: ReadonlyArray, + onProgres?: () => void + ): Promise; installStickerPack(packId: string, key: string): void; } } @@ -25,172 +21,33 @@ export type UploadOptions = Readonly<{ onProgress?: () => void; }>; -export type UploadResult = Readonly<{ - key: string; - packId: string; -}>; - -async function getSchemas() { - const UploadAttributes = z.object({ - acl: z.string(), - algorithm: z.string(), - credential: z.string(), - date: z.string(), - id: z.number(), - key: z.string(), - policy: z.string(), - signature: z.string(), - securityToken: z.string(), - }); - - const FormResponse = z.object({ - packId: z.string(), - manifest: UploadAttributes, - art: z.array(UploadAttributes), - uploadURL: z.string(), - }); - - return { UploadAttributes, FormResponse }; -} - -type Schemas = Awaited>; - -const encoder = new TextEncoder(); - export class APIError extends Error { constructor(message: string, public readonly errorMessageI18nKey: string) { super(message); } } +export type UploadResult = Readonly<{ + key: string; + packId: string; +}>; + export async function upload( encryptResult: EncryptResult, - { artType, onProgress }: UploadOptions + { onProgress }: UploadOptions ): Promise { - const { - encryptedManifest: manifest, - encryptedImages: images, - key, - } = encryptResult; + const { encryptedManifest, encryptedImages, key } = encryptResult; - const credentials = await window.getCredentials(); - const { baseUrl = '' } = credentials; - - const auth = b64.fromByteArray( - encoder.encode([credentials.username, credentials.password].join(':')) + const packId = await window.uploadStickerPack( + encryptedManifest, + encryptedImages, + onProgress ); - const res = await fetch( - `${baseUrl}/api/form?artType=${artType}&artCount=${images.length}`, - { - headers: { - authorization: `Basic ${auth}`, - }, - } - ); - - if (res.status === 401 || res.status === 403) { - throw new APIError( - 'Credentials expired', - 'StickerCreator--Toasts--expired-credenitals' - ); - } - - if (!res.ok) { - throw new Error(`Request failed, status: ${res.status}`); - } - - const { FormResponse } = await getSchemas(); - - const form = FormResponse.parse(await res.json()); - if (form.art.length !== images.length) { - throw new Error('Invalid form data, image count mismatch'); - } - - const limiter = pLimit(MAX_PARALLEL_UPLOADS); - - await Promise.all([ - limiter(async () => { - await uploadAttachment(form.uploadURL, form.manifest, manifest); - onProgress?.(); - }), - ...images.map((image, index) => - limiter(async () => { - await uploadAttachment(form.uploadURL, form.art[index], image); - onProgress?.(); - }) - ), - ]); - - window.installStickerPack(form.packId, key); + window.installStickerPack(packId, key); return { key, - packId: form.packId, + packId, }; } - -async function uploadAttachment( - uploadURL: string, - { - key, - credential, - acl, - algorithm, - date, - policy, - signature, - securityToken, - }: zInfer, - encryptedData: Uint8Array -): Promise { - // Note: when using the boundary string in the POST body, it needs to be - // prefixed by an extra --, and the final boundary string at the end gets a - // -- prefix and a -- suffix. - const boundaryString = getRandomString().replace(/=/g, ''); - const CRLF = '\r\n'; - const getSection = (name: string, value: string) => - [ - `--${boundaryString}`, - `Content-Disposition: form-data; name="${name}"${CRLF}`, - value, - ].join(CRLF); - - const start = [ - getSection('key', key), - getSection('x-amz-credential', credential), - getSection('acl', acl), - getSection('x-amz-algorithm', algorithm), - getSection('x-amz-date', date), - getSection('policy', policy), - getSection('x-amz-signature', signature), - getSection('x-amz-security-token', securityToken), - getSection('Content-Type', 'application/octet-stream'), - `--${boundaryString}`, - 'Content-Disposition: form-data; name="file"', - `Content-Type: application/octet-stream${CRLF}${CRLF}`, - ].join(CRLF); - const end = `${CRLF}--${boundaryString}--${CRLF}`; - - const startBuffer = encoder.encode(start); - const endBuffer = encoder.encode(end); - - const contentLength = - startBuffer.length + encryptedData.length + endBuffer.length; - const body = new Uint8Array(contentLength); - body.set(startBuffer, 0); - body.set(encryptedData, startBuffer.length); - body.set(endBuffer, startBuffer.length + encryptedData.length); - - const res = await fetch(uploadURL, { - method: 'POST', - headers: { - 'content-length': contentLength.toString(), - 'content-type': `multipart/form-data; boundary=${boundaryString}`, - }, - body, - }); - if (!res.ok) { - throw new Error('Failed to upload attachment'); - } -} diff --git a/sticker-creator/yarn.lock b/sticker-creator/yarn.lock index e29ae0412d9..0a9d4da822d 100644 --- a/sticker-creator/yarn.lock +++ b/sticker-creator/yarn.lock @@ -3108,13 +3108,6 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" -p-limit@4.0.0, p-limit@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-4.0.0.tgz#914af6544ed32bfa54670b061cafcbd04984b644" - integrity sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ== - dependencies: - yocto-queue "^1.0.0" - p-limit@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" @@ -3122,6 +3115,13 @@ p-limit@^3.0.2: dependencies: yocto-queue "^0.1.0" +p-limit@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-4.0.0.tgz#914af6544ed32bfa54670b061cafcbd04984b644" + integrity sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ== + dependencies: + yocto-queue "^1.0.0" + p-locate@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" diff --git a/ts/components/GlobalModalContainer.tsx b/ts/components/GlobalModalContainer.tsx index 60868be521e..76f94b49c72 100644 --- a/ts/components/GlobalModalContainer.tsx +++ b/ts/components/GlobalModalContainer.tsx @@ -3,7 +3,6 @@ import React from 'react'; import type { - AuthorizeArtCreatorDataType, ContactModalStateType, DeleteMessagesPropsType, EditHistoryMessagesType, @@ -96,11 +95,6 @@ export type PropsType = { // UsernameOnboarding usernameOnboardingState: UsernameOnboardingState; renderUsernameOnboarding: () => JSX.Element; - // AuthArtCreatorModal - authArtCreatorData?: AuthorizeArtCreatorDataType; - isAuthorizingArtCreator?: boolean; - cancelAuthorizeArtCreator: () => unknown; - confirmAuthorizeArtCreator: () => unknown; }; export function GlobalModalContainer({ @@ -166,11 +160,6 @@ export function GlobalModalContainer({ // UsernameOnboarding usernameOnboardingState, renderUsernameOnboarding, - // AuthArtCreatorModal - authArtCreatorData, - isAuthorizingArtCreator, - cancelAuthorizeArtCreator, - confirmAuthorizeArtCreator, }: PropsType): JSX.Element | null { // We want the following dialogs to show in this order: // 1. Errors @@ -289,28 +278,5 @@ export function GlobalModalContainer({ ); } - if (authArtCreatorData) { - return ( - - {i18n('icu:AuthArtCreator--dialog--message')} - - ); - } - return null; } diff --git a/ts/state/ducks/globalModals.ts b/ts/state/ducks/globalModals.ts index 304607f5669..ae3c0f83454 100644 --- a/ts/state/ducks/globalModals.ts +++ b/ts/state/ducks/globalModals.ts @@ -18,7 +18,6 @@ import type { RecipientsByConversation } from './stories'; import type { SafetyNumberChangeSource } from '../../components/SafetyNumberChangeDialog'; import type { EditState as ProfileEditorEditState } from '../../components/ProfileEditor'; import type { StateType as RootStateType } from '../reducer'; -import * as Errors from '../../types/errors'; import * as SingleServePromise from '../../services/singleServePromise'; import * as Stickers from '../../types/Stickers'; import { UsernameOnboardingState } from '../../types/globalModals'; @@ -28,18 +27,13 @@ import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions'; import { longRunningTaskWrapper } from '../../util/longRunningTaskWrapper'; import { useBoundActions } from '../../hooks/useBoundActions'; import { isGroupV1 } from '../../util/whatTypeOfConversation'; -import { authorizeArtCreator } from '../../textsecure/authorizeArtCreator'; -import type { AuthorizeArtCreatorOptionsType } from '../../textsecure/authorizeArtCreator'; import { getGroupMigrationMembers } from '../../groups'; -import { ToastType } from '../../types/Toast'; import { MESSAGE_CHANGED, MESSAGE_DELETED, MESSAGE_EXPIRED, actions as conversationsActions, } from './conversations'; -import { SHOW_TOAST } from './toast'; -import type { ShowToastActionType } from './toast'; import { isDownloaded } from '../../types/Attachment'; import type { ButtonVariant } from '../../components/Button'; import type { MessageRequestState } from '../../components/conversation/MessageRequestActionsConfirmation'; @@ -73,8 +67,6 @@ export type SafetyNumberChangedBlockingDataType = ReadonlyDeep<{ promiseUuid: SingleServePromise.SingleServePromiseIdString; source?: SafetyNumberChangeSource; }>; -export type AuthorizeArtCreatorDataType = - ReadonlyDeep; type MigrateToGV2PropsType = ReadonlyDeep<{ areWeInvited: boolean; @@ -87,7 +79,6 @@ type MigrateToGV2PropsType = ReadonlyDeep<{ export type GlobalModalsStateType = ReadonlyDeep<{ addUserToAnotherGroupModalContactId?: string; aboutContactModalContactId?: string; - authArtCreatorData?: AuthorizeArtCreatorDataType; contactModalState?: ContactModalStateType; deleteMessagesProps?: DeleteMessagesPropsType; editHistoryMessages?: EditHistoryMessagesType; @@ -100,7 +91,6 @@ export type GlobalModalsStateType = ReadonlyDeep<{ forwardMessagesProps?: ForwardMessagesPropsType; gv2MigrationProps?: MigrateToGV2PropsType; hasConfirmationModal: boolean; - isAuthorizingArtCreator?: boolean; isProfileEditorVisible: boolean; isShortcutGuideModalVisible: boolean; isSignalConnectionsVisible: boolean; @@ -157,13 +147,7 @@ const TOGGLE_MESSAGE_REQUEST_ACTIONS_CONFIRMATION = 'globalModals/TOGGLE_MESSAGE_REQUEST_ACTIONS_CONFIRMATION'; const CLOSE_SHORTCUT_GUIDE_MODAL = 'globalModals/CLOSE_SHORTCUT_GUIDE_MODAL'; const SHOW_SHORTCUT_GUIDE_MODAL = 'globalModals/SHOW_SHORTCUT_GUIDE_MODAL'; -const SHOW_AUTH_ART_CREATOR = 'globalModals/SHOW_AUTH_ART_CREATOR'; const TOGGLE_CONFIRMATION_MODAL = 'globalModals/TOGGLE_CONFIRMATION_MODAL'; -const CANCEL_AUTH_ART_CREATOR = 'globalModals/CANCEL_AUTH_ART_CREATOR'; -const CONFIRM_AUTH_ART_CREATOR_PENDING = - 'globalModals/CONFIRM_AUTH_ART_CREATOR_PENDING'; -const CONFIRM_AUTH_ART_CREATOR_FULFILLED = - 'globalModals/CONFIRM_AUTH_ART_CREATOR_FULFILLED'; const SHOW_EDIT_HISTORY_MODAL = 'globalModals/SHOW_EDIT_HISTORY_MODAL'; const CLOSE_EDIT_HISTORY_MODAL = 'globalModals/CLOSE_EDIT_HISTORY_MODAL'; const TOGGLE_USERNAME_ONBOARDING = 'globalModals/TOGGLE_USERNAME_ONBOARDING'; @@ -332,23 +316,6 @@ type ShowShortcutGuideModalActionType = ReadonlyDeep<{ type: typeof SHOW_SHORTCUT_GUIDE_MODAL; }>; -export type ShowAuthArtCreatorActionType = ReadonlyDeep<{ - type: typeof SHOW_AUTH_ART_CREATOR; - payload: AuthorizeArtCreatorDataType; -}>; - -type CancelAuthArtCreatorActionType = ReadonlyDeep<{ - type: typeof CANCEL_AUTH_ART_CREATOR; -}>; - -type ConfirmAuthArtCreatorPendingActionType = ReadonlyDeep<{ - type: typeof CONFIRM_AUTH_ART_CREATOR_PENDING; -}>; - -type ConfirmAuthArtCreatorFulfilledActionType = ReadonlyDeep<{ - type: typeof CONFIRM_AUTH_ART_CREATOR_FULFILLED; -}>; - type ShowEditHistoryModalActionType = ReadonlyDeep<{ type: typeof SHOW_EDIT_HISTORY_MODAL; payload: { @@ -361,14 +328,11 @@ type CloseEditHistoryModalActionType = ReadonlyDeep<{ }>; export type GlobalModalsActionType = ReadonlyDeep< - | CancelAuthArtCreatorActionType | CloseEditHistoryModalActionType | CloseErrorModalActionType | CloseGV2MigrationDialogActionType | CloseShortcutGuideModalActionType | CloseStickerPackPreviewActionType - | ConfirmAuthArtCreatorFulfilledActionType - | ConfirmAuthArtCreatorPendingActionType | HideContactModalActionType | HideSendAnywayDialogActiontype | HideStoriesSettingsActionType @@ -377,7 +341,6 @@ export type GlobalModalsActionType = ReadonlyDeep< | MessageChangedActionType | MessageDeletedActionType | MessageExpiredActionType - | ShowAuthArtCreatorActionType | ShowContactModalActionType | ShowEditHistoryModalActionType | ShowErrorModalActionType @@ -406,19 +369,16 @@ export type GlobalModalsActionType = ReadonlyDeep< // Action Creators export const actions = { - cancelAuthorizeArtCreator, closeEditHistoryModal, closeErrorModal, closeGV2MigrationDialog, closeShortcutGuideModal, closeStickerPackPreview, - confirmAuthorizeArtCreator, hideBlockingSafetyNumberChangeDialog, hideContactModal, hideStoriesSettings, hideUserNotFoundModal, hideWhatsNewModal, - showAuthorizeArtCreator, showBlockingSafetyNumberChangeDialog, showContactModal, showEditHistoryModal, @@ -790,25 +750,6 @@ function showShortcutGuideModal(): ShowShortcutGuideModalActionType { }; } -function cancelAuthorizeArtCreator(): ThunkAction< - void, - RootStateType, - unknown, - CancelAuthArtCreatorActionType -> { - return async (dispatch, getState) => { - const data = getState().globalModals.authArtCreatorData; - - if (!data) { - return; - } - - dispatch({ - type: CANCEL_AUTH_ART_CREATOR, - }); - }; -} - function copyOverMessageAttributesIntoEditHistory( messageAttributes: ReadonlyDeep ): EditHistoryMessagesType | undefined { @@ -860,54 +801,6 @@ function closeEditHistoryModal(): CloseEditHistoryModalActionType { }; } -export function showAuthorizeArtCreator( - data: AuthorizeArtCreatorDataType -): ShowAuthArtCreatorActionType { - return { - type: SHOW_AUTH_ART_CREATOR, - payload: data, - }; -} - -export function confirmAuthorizeArtCreator(): ThunkAction< - void, - RootStateType, - unknown, - | ConfirmAuthArtCreatorPendingActionType - | ConfirmAuthArtCreatorFulfilledActionType - | CancelAuthArtCreatorActionType - | ShowToastActionType -> { - return async (dispatch, getState) => { - const data = getState().globalModals.authArtCreatorData; - - if (!data) { - dispatch({ type: CANCEL_AUTH_ART_CREATOR }); - return; - } - - dispatch({ - type: CONFIRM_AUTH_ART_CREATOR_PENDING, - }); - - try { - await authorizeArtCreator(data); - } catch (err) { - log.error('authorizeArtCreator failed', Errors.toLogFormat(err)); - dispatch({ - type: SHOW_TOAST, - payload: { - toastType: ToastType.Error, - }, - }); - } - - dispatch({ - type: CONFIRM_AUTH_ART_CREATOR_FULFILLED, - }); - }; -} - function copyOverMessageAttributesIntoForwardMessages( messagesProps: ReadonlyArray, attributes: ReadonlyDeep @@ -1168,36 +1061,6 @@ export function reducer( }; } - if (action.type === CANCEL_AUTH_ART_CREATOR) { - return { - ...state, - authArtCreatorData: undefined, - }; - } - - if (action.type === SHOW_AUTH_ART_CREATOR) { - return { - ...state, - isAuthorizingArtCreator: false, - authArtCreatorData: action.payload, - }; - } - - if (action.type === CONFIRM_AUTH_ART_CREATOR_PENDING) { - return { - ...state, - isAuthorizingArtCreator: true, - }; - } - - if (action.type === CONFIRM_AUTH_ART_CREATOR_FULFILLED) { - return { - ...state, - isAuthorizingArtCreator: false, - authArtCreatorData: undefined, - }; - } - if (action.type === SHOW_EDIT_HISTORY_MODAL) { return { ...state, diff --git a/ts/state/smart/GlobalModalContainer.tsx b/ts/state/smart/GlobalModalContainer.tsx index 58929358477..c42dffc4077 100644 --- a/ts/state/smart/GlobalModalContainer.tsx +++ b/ts/state/smart/GlobalModalContainer.tsx @@ -90,7 +90,6 @@ export const SmartGlobalModalContainer = memo( const { aboutContactModalContactId, addUserToAnotherGroupModalContactId, - authArtCreatorData, contactModalState, deleteMessagesProps, editHistoryMessages, @@ -99,7 +98,6 @@ export const SmartGlobalModalContainer = memo( forwardMessagesProps, messageRequestActionsConfirmationProps, notePreviewModalProps, - isAuthorizingArtCreator, isProfileEditorVisible, isShortcutGuideModalVisible, isSignalConnectionsVisible, @@ -113,9 +111,7 @@ export const SmartGlobalModalContainer = memo( } = useSelector(getGlobalModalsState); const { - cancelAuthorizeArtCreator, closeErrorModal, - confirmAuthorizeArtCreator, hideUserNotFoundModal, hideWhatsNewModal, toggleSignalConnectionsModal, @@ -218,10 +214,6 @@ export const SmartGlobalModalContainer = memo( toggleSignalConnectionsModal={toggleSignalConnectionsModal} userNotFoundModalState={userNotFoundModalState} usernameOnboardingState={usernameOnboardingState} - isAuthorizingArtCreator={isAuthorizingArtCreator} - authArtCreatorData={authArtCreatorData} - cancelAuthorizeArtCreator={cancelAuthorizeArtCreator} - confirmAuthorizeArtCreator={confirmAuthorizeArtCreator} /> ); } diff --git a/ts/test-node/util/signalRoutes_test.ts b/ts/test-node/util/signalRoutes_test.ts index 52b028b6b95..dd0dd1a85a4 100644 --- a/ts/test-node/util/signalRoutes_test.ts +++ b/ts/test-node/util/signalRoutes_test.ts @@ -142,16 +142,6 @@ describe('signalRoutes', () => { check(`sgnl://signal.link/call#key=${foo}`, result); }); - it('artAuth', () => { - const result: ParsedSignalRoute = { - key: 'artAuth', - args: { token: foo, pubKey: foo }, - }; - const check = createCheck({ hasWebUrl: false }); - check(`sgnl://art-auth/?token=${foo}&pub_key=${foo}`, result); - check(`sgnl://art-auth?token=${foo}&pub_key=${foo}`, result); - }); - it('artAddStickers', () => { const result: ParsedSignalRoute = { key: 'artAddStickers', diff --git a/ts/textsecure/SocketManager.ts b/ts/textsecure/SocketManager.ts index 07b059c1a5c..ce067160c8a 100644 --- a/ts/textsecure/SocketManager.ts +++ b/ts/textsecure/SocketManager.ts @@ -52,7 +52,6 @@ export const AUTHENTICATED_CHANNEL_NAME = 'authenticated'; export type SocketManagerOptions = Readonly<{ url: string; - artCreatorUrl: string; certificateAuthority: string; version: string; proxyUrl?: string; diff --git a/ts/textsecure/WebAPI.ts b/ts/textsecure/WebAPI.ts index 268e0390662..8000820ebc3 100644 --- a/ts/textsecure/WebAPI.ts +++ b/ts/textsecure/WebAPI.ts @@ -14,7 +14,6 @@ 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 { Net } from '@signalapp/libsignal-client'; import { assertDev, strictAssert } from '../util/assert'; @@ -554,7 +553,6 @@ const URL_CALLS = { getOnboardingStoryManifest: 'dynamic/desktop/stories/onboarding/manifest.json', getStickerPackUpload: 'v1/sticker/pack/form', - getArtAuth: 'v1/art/auth', getBackupCredentials: 'v1/archives/auth', getBackupCDNCredentials: 'v1/archives/auth/read', getBackupUploadForm: 'v1/archives/upload/form', @@ -647,7 +645,6 @@ type InitializeOptionsType = { storageUrl: string; updatesUrl: string; resourcesUrl: string; - artCreatorUrl: string; cdnUrlObject: { readonly '0': string; readonly [propName: string]: string; @@ -975,13 +972,6 @@ export type ReportMessageOptionsType = Readonly<{ token?: string; }>; -const artAuthZod = z.object({ - username: z.string(), - password: z.string(), -}); - -export type ArtAuthType = z.infer; - const attachmentV3Response = z.object({ cdn: z.literal(2).or(z.literal(3)), key: z.string(), @@ -1149,6 +1139,23 @@ export type GetBackupInfoResponseType = z.infer< typeof getBackupInfoResponseSchema >; +const StickerPackUploadAttributesSchema = z.object({ + acl: z.string(), + algorithm: z.string(), + credential: z.string(), + date: z.string(), + id: z.number(), + key: z.string(), + policy: z.string(), + signature: z.string(), +}); + +const StickerPackUploadFormSchema = z.object({ + packId: z.string(), + manifest: StickerPackUploadAttributesSchema, + stickers: z.array(StickerPackUploadAttributesSchema), +}); + export type WebAPIType = { startRegistration(): unknown; finishRegistration(baton: unknown): void; @@ -1166,7 +1173,6 @@ export type WebAPIType = { version: string, imageFiles: Array ) => Promise>; - getArtAuth: () => Promise; getAttachmentFromBackupTier: (args: { mediaId: string; backupDir: string; @@ -1237,7 +1243,6 @@ export type WebAPIType = { getProvisioningResource: ( handler: IRequestHandler ) => Promise; - getArtProvisioningSocket: (token: string) => Promise; getSenderCertificate: ( withUuid?: boolean ) => Promise; @@ -1280,7 +1285,7 @@ export type WebAPIType = { ) => Promise; putStickers: ( encryptedManifest: Uint8Array, - encryptedStickers: Array, + encryptedStickers: ReadonlyArray, onProgress?: () => void ) => Promise; reserveUsername: ( @@ -1471,7 +1476,6 @@ export function initialize({ storageUrl, updatesUrl, resourcesUrl, - artCreatorUrl, directoryConfig, cdnUrlObject, certificateAuthority, @@ -1493,9 +1497,6 @@ 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'); } @@ -1558,7 +1559,6 @@ export function initialize({ const socketManager = new SocketManager(libsignalNet, { url, - artCreatorUrl, certificateAuthority, version, proxyUrl, @@ -1667,8 +1667,6 @@ export function initialize({ fetchLinkPreviewMetadata, finishRegistration, getAccountForUsername, - getArtAuth, - getArtProvisioningSocket, getAttachment, getAttachmentFromBackupTier, getAvatar, @@ -3309,20 +3307,19 @@ export function initialize({ async function putStickers( encryptedManifest: Uint8Array, - encryptedStickers: Array, + encryptedStickers: ReadonlyArray, onProgress?: () => void ) { // Get manifest and sticker upload parameters - const { packId, manifest, stickers } = (await _ajax({ + const formJson = await _ajax({ call: 'getStickerPackUpload', responseType: 'json', httpType: 'GET', urlParameters: `/${encryptedStickers.length}`, - })) as { - packId: string; - manifest: ServerV2AttachmentType; - stickers: ReadonlyArray; - }; + }); + + const { packId, manifest, stickers } = + StickerPackUploadFormSchema.parse(formJson); // Upload manifest const manifestParams = makePutParams(manifest, encryptedManifest); @@ -4008,15 +4005,6 @@ export function initialize({ return socketManager.getProvisioningResource(handler); } - function getArtProvisioningSocket(token: string): Promise { - return socketManager.connectExternalSocket({ - url: `${artCreatorUrl}/api/socket?token=${token}`, - extraHeaders: { - origin: artCreatorUrl, - }, - }); - } - async function cdsLookup({ e164s, acisAndAccessKeys = [], @@ -4030,19 +4018,5 @@ export function initialize({ useLibsignal, }); } - - // - // Art - // - - async function getArtAuth(): Promise { - const response = await _ajax({ - call: 'getArtAuth', - httpType: 'GET', - responseType: 'json', - }); - - return artAuthZod.parse(response); - } } } diff --git a/ts/textsecure/authorizeArtCreator.ts b/ts/textsecure/authorizeArtCreator.ts deleted file mode 100644 index 167575e779a..00000000000 --- a/ts/textsecure/authorizeArtCreator.ts +++ /dev/null @@ -1,59 +0,0 @@ -// 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 { - 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({ - plaintext: 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'); - } -} diff --git a/ts/types/RendererConfig.ts b/ts/types/RendererConfig.ts index c4390bd30ae..ad363f9236a 100644 --- a/ts/types/RendererConfig.ts +++ b/ts/types/RendererConfig.ts @@ -30,7 +30,6 @@ export type DirectoryConfigType = z.infer; export const rendererConfigSchema = z.object({ appInstance: configOptionalStringSchema, appStartInitialSpellcheckSetting: z.boolean(), - artCreatorUrl: configRequiredStringSchema, buildCreation: z.number(), buildExpiration: z.number(), cdnUrl0: configRequiredStringSchema, diff --git a/ts/util/createIPCEvents.ts b/ts/util/createIPCEvents.ts index 7827479e624..4f070b514a6 100644 --- a/ts/util/createIPCEvents.ts +++ b/ts/util/createIPCEvents.ts @@ -16,7 +16,6 @@ import * as Errors from '../types/errors'; import * as Stickers from '../types/Stickers'; import type { ConversationType } from '../state/ducks/conversations'; -import type { AuthorizeArtCreatorDataType } from '../state/ducks/globalModals'; import { calling } from '../services/calling'; import { resolveUsernameByLinkBase64 } from '../services/username'; import { writeProfile } from '../services/writeProfile'; @@ -96,7 +95,6 @@ export type IPCEventsValuesType = { }; export type IPCEventsCallbacksType = { - openArtCreator(): Promise; getAvailableIODevices(): Promise<{ availableCameras: Array< Pick @@ -106,7 +104,6 @@ export type IPCEventsCallbacksType = { }>; addCustomColor: (customColor: CustomColorType) => void; addDarkOverlay: () => void; - authorizeArtCreator: (data: AuthorizeArtCreatorDataType) => void; deleteAllData: () => Promise; deleteAllMyStories: () => Promise; editCustomColor: (colorId: string, customColor: CustomColorType) => void; @@ -141,6 +138,10 @@ export type IPCEventsCallbacksType = { customColor?: { id: string; value: CustomColorType } ) => void; getDefaultConversationColor: () => DefaultConversationColorType; + uploadStickerPack: ( + manifest: Uint8Array, + stickers: ReadonlyArray + ) => Promise; }; type ValuesWithGetters = Omit< @@ -239,15 +240,6 @@ export function createIPCEvents( }; return { - openArtCreator: async () => { - const auth = await window.textsecure.server?.getArtAuth(); - if (!auth) { - return; - } - - window.openArtCreator(auth); - }, - getDeviceName: () => window.textsecure.storage.user.getDeviceName(), getPhoneNumber: () => { try { @@ -530,14 +522,6 @@ export function createIPCEvents( }); document.body.prepend(newOverlay); }, - authorizeArtCreator: (data: AuthorizeArtCreatorDataType) => { - // We can get these events even if the user has never linked this instance. - if (!Registration.everDone()) { - log.warn('authorizeArtCreator: Not registered, returning early'); - return; - } - window.reduxActions.globalModals.showAuthorizeArtCreator(data); - }, removeDarkOverlay: () => { const elems = document.querySelectorAll('.dark-overlay'); @@ -717,6 +701,16 @@ export function createIPCEvents( } }, + uploadStickerPack: ( + manifest: Uint8Array, + stickers: ReadonlyArray + ): Promise => { + strictAssert(window.textsecure.server, 'WebAPI must be available'); + return window.textsecure.server.putStickers(manifest, stickers, () => + ipcRenderer.send('art-creator:onUploadProgress') + ); + }, + ...overrideEvents, }; } diff --git a/ts/util/signalRoutes.ts b/ts/util/signalRoutes.ts index adc1857c539..9a5855d68b7 100644 --- a/ts/util/signalRoutes.ts +++ b/ts/util/signalRoutes.ts @@ -439,38 +439,6 @@ export const artAddStickersRoute = _route('artAddStickers', { }, }); -/** - * Art Service Authentication - * @example - * ```ts - * artAuthRoute.toAppUrl({ - * token: "123", - * pubKey: "abc", - * }) - * // URL { "sgnl://art-auth?token=123&pub_key=abc" } - */ -export const artAuthRoute = _route('artAuth', { - patterns: [_pattern('sgnl:', 'art-auth', '{/}?', { search: ':params' })], - schema: z.object({ - token: paramSchema, // opaque - pubKey: paramSchema, // base64url - }), - parse(result) { - const params = new URLSearchParams(result.search.groups.params); - return { - token: params.get('token'), - pubKey: params.get('pub_key'), - }; - }, - toAppUrl(args) { - const params = new URLSearchParams({ - token: args.token, - pub_key: args.pubKey, - }); - return new URL(`sgnl://art-auth?${params.toString()}`); - }, -}); - /** * Show a conversation * @example @@ -594,7 +562,6 @@ const _allSignalRoutes = [ captchaRoute, linkCallRoute, artAddStickersRoute, - artAuthRoute, showConversationRoute, startCallLobbyRoute, showWindowRoute, diff --git a/ts/windows/main/phase1-ipc.ts b/ts/windows/main/phase1-ipc.ts index 08b74abc0dc..b25f22fed21 100644 --- a/ts/windows/main/phase1-ipc.ts +++ b/ts/windows/main/phase1-ipc.ts @@ -341,24 +341,6 @@ ipc.on('show-group-via-link', (_event, info) => { drop(window.Events.showGroupViaLink?.(info.value)); }); -ipc.on('open-art-creator', () => { - drop(window.Events.openArtCreator()); -}); - -window.openArtCreator = ({ - username, - password, -}: { - username: string; - password: string; -}) => { - return ipc.invoke('open-art-creator', { username, password }); -}; - -ipc.on('authorize-art-creator', (_event, info) => { - window.Events.authorizeArtCreator?.(info); -}); - ipc.on('start-call-lobby', (_event, { conversationId }) => { window.IPC.showWindow(); window.reduxActions?.calling?.startCallingLobby({ @@ -458,3 +440,18 @@ ipc.on('show-release-notes', () => { showReleaseNotes(); } }); + +ipc.on( + 'art-creator:uploadStickerPack', + async ( + event, + { + manifest, + stickers, + }: { manifest: Uint8Array; stickers: ReadonlyArray } + ) => { + const packId = await window.Events?.uploadStickerPack(manifest, stickers); + + event.sender.send('art-creator:uploadStickerPack:done', packId); + } +); diff --git a/ts/windows/main/phase2-dependencies.ts b/ts/windows/main/phase2-dependencies.ts index f985a08777c..9775cf3098a 100644 --- a/ts/windows/main/phase2-dependencies.ts +++ b/ts/windows/main/phase2-dependencies.ts @@ -27,7 +27,6 @@ window.WebAPI = window.textsecure.WebAPI.initialize({ storageUrl: config.storageUrl, updatesUrl: config.updatesUrl, resourcesUrl: config.resourcesUrl, - artCreatorUrl: config.artCreatorUrl, directoryConfig: config.directoryConfig, cdnUrlObject: { 0: config.cdnUrl0, diff --git a/ts/windows/sticker-creator/preload.ts b/ts/windows/sticker-creator/preload.ts index 2eba6cf9609..2c55d8607e4 100644 --- a/ts/windows/sticker-creator/preload.ts +++ b/ts/windows/sticker-creator/preload.ts @@ -3,8 +3,26 @@ import { contextBridge, ipcRenderer } from 'electron'; -contextBridge.exposeInMainWorld('getCredentials', async () => - ipcRenderer.invoke('get-art-creator-auth') +let onProgress: (() => void) | undefined; + +ipcRenderer.on('art-creator:onUploadProgress', () => { + onProgress?.(); +}); + +contextBridge.exposeInMainWorld( + 'uploadStickerPack', + async ( + manifest: Uint8Array, + stickers: Readonly, + newOnProgress: (() => void) | undefined + ): Promise => { + onProgress = newOnProgress; + + return ipcRenderer.invoke('art-creator:uploadStickerPack', { + manifest, + stickers, + }); + } ); contextBridge.exposeInMainWorld(