Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@signalapp/mock-server

Package Overview
Dependencies
Maintainers
6
Versions
115
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@signalapp/mock-server - npm Package Compare versions

Comparing version 7.1.3 to 8.0.0

2

package.json
{
"name": "@signalapp/mock-server",
"version": "7.1.3",
"version": "8.0.0",
"description": "Mock Signal Server for writing tests",

@@ -5,0 +5,0 @@ "main": "src/index.js",

/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
import { type Readable } from 'stream';
import Long from 'long';
import { ServerOptions } from 'https';
import { BackupMediaBatch } from '../data/schemas';
import { ProvisionIdString, ServiceIdKind, ServiceIdString } from '../types';
import { Group as GroupData } from '../data/group';
import { signalservice as Proto } from '../../protos/compiled';
import { Server as BaseServer, ChallengeResponse, EnvelopeType, IsSendRateLimitedOptions, ModifyGroupOptions, ModifyGroupResult, ProvisionDeviceOptions, ProvisioningResponse } from '../server/base';
import { BackupMediaBatchResponse, Server as BaseServer, ChallengeResponse, EnvelopeType, IsSendRateLimitedOptions, ModifyGroupOptions, ModifyGroupResult, ProvisionDeviceOptions, ProvisioningResponse } from '../server/base';
import { Device, DeviceKeys } from '../data/device';

@@ -76,3 +79,5 @@ import { PrimaryDevice } from './primary-device';

stopRateLimiting({ source, target, }: RateLimitOptions): number | undefined;
storeAttachmentOnCdn(cdnNumber: number, cdnKey: string, data: Uint8Array): void;
removeAllCDNAttachments(): Promise<void>;
storeAttachmentOnCdn(cdnNumber: number, cdnKey: string, data: Uint8Array): Promise<void>;
storeBackupOnCdn(backupId: Uint8Array, stream: Readable): Promise<void>;
getProvisioningResponse(id: ProvisionIdString): Promise<ProvisioningResponse>;

@@ -87,2 +92,3 @@ handleMessage(source: Device | undefined, serviceIdKind: ServiceIdKind, envelopeType: EnvelopeType, target: Device, encrypted: Buffer): Promise<void>;

protected onStorageManifestUpdate(device: Device, version: Long): Promise<void>;
protected backupTransitAttachments(backupId: string, batch: BackupMediaBatch): Promise<Array<BackupMediaBatchResponse>>;
private createQueue;

@@ -89,0 +95,0 @@ private generateNumber;

@@ -11,2 +11,4 @@ "use strict";

const fs_1 = __importDefault(require("fs"));
const promises_1 = __importDefault(require("fs/promises"));
const promises_2 = require("stream/promises");
const path_1 = __importDefault(require("path"));

@@ -259,9 +261,29 @@ const https_1 = __importDefault(require("https"));

}
storeAttachmentOnCdn(cdnNumber, cdnKey, data) {
async removeAllCDNAttachments() {
const { cdn3Path } = this.config;
(0, assert_1.default)(cdn3Path, 'cdn3Path must be provided to store attachments');
const dir = path_1.default.join(cdn3Path, 'attachments');
await promises_1.default.rm(dir, {
recursive: true,
});
}
async storeAttachmentOnCdn(cdnNumber, cdnKey, data) {
assert_1.default.strictEqual(cdnNumber, 3, 'Only cdn 3 currently supported');
const { cdn3Path } = this.config;
(0, assert_1.default)(cdn3Path, 'cdn3Path must be provided to store attachments');
fs_1.default.mkdirSync(path_1.default.join(cdn3Path, 'attachments'), { recursive: true });
fs_1.default.writeFileSync(path_1.default.join(cdn3Path, 'attachments', cdnKey), data);
const dir = path_1.default.join(cdn3Path, 'attachments');
await promises_1.default.mkdir(dir, {
recursive: true,
});
await promises_1.default.writeFile(path_1.default.join(dir, cdnKey), data);
}
async storeBackupOnCdn(backupId, stream) {
const { cdn3Path } = this.config;
(0, assert_1.default)(cdn3Path, 'cdn3Path must be provided to store attachments');
const dir = path_1.default.join(cdn3Path, 'backups', Buffer.from(backupId).toString('base64url'));
await promises_1.default.mkdir(dir, {
recursive: true,
});
await (0, promises_2.pipeline)(stream, fs_1.default.createWriteStream(path_1.default.join(dir, 'backup')));
}
//

@@ -422,2 +444,49 @@ // Implement Server's abstract methods

}
async backupTransitAttachments(backupId, batch) {
const { cdn3Path } = this.config;
(0, assert_1.default)(cdn3Path, 'cdn3Path must be provided to store attachments');
const dir = path_1.default.join(cdn3Path, 'backups');
const mediaDir = path_1.default.join(dir, backupId, 'media');
await promises_1.default.mkdir(mediaDir, {
recursive: true,
});
return Promise.all(batch.items.map(async (item) => {
assert_1.default.strictEqual(item.sourceAttachment.cdn, 3, 'Invalid object CDN');
const transitPath = path_1.default.join(dir, item.sourceAttachment.key);
const finalPath = path_1.default.join(mediaDir, item.mediaId);
// TODO(indutny): streams
let data;
try {
data = await promises_1.default.readFile(transitPath);
}
catch (error) {
(0, assert_1.default)(error instanceof Error);
if ('code' in error && error.code === 'ENOENT') {
return {
cdn: 3,
status: 404,
mediaId: item.mediaId,
};
}
throw error;
}
assert_1.default.strictEqual(data.byteLength, item.objectLength, 'Invalid objectLength');
const reencrypted = (0, crypto_1.encryptAttachment)(data, {
aesKey: item.encryptionKey,
macKey: item.hmacKey,
iv: item.iv,
});
await promises_1.default.writeFile(finalPath, reencrypted.blob);
this.onNewBackupMediaObject(backupId, {
cdn: 3,
mediaId: item.mediaId,
objectLength: reencrypted.blob.length,
});
return {
cdn: 3,
status: 200,
mediaId: item.mediaId,
};
}));
}
//

@@ -424,0 +493,0 @@ // Private

@@ -24,3 +24,8 @@ /// <reference types="node" />

export declare function encryptProvisionMessage(data: Buffer, remotePubKey: PublicKey): EncryptedProvisionMessage;
export declare function encryptAttachment(cleartext: Buffer): Attachment;
export type EncryptAttachmentOptions = Readonly<{
aesKey: Buffer;
macKey: Buffer;
iv: Buffer;
}>;
export declare function encryptAttachment(cleartext: Buffer, { aesKey, macKey, iv }?: EncryptAttachmentOptions): Attachment;
export declare function generateServerCertificate(rootKey: PrivateKey): ServerCertificate;

@@ -27,0 +32,0 @@ export declare function generateSenderCertificate(serverCert: ServerCertificate, sender: Sender): SenderCertificate;

@@ -40,6 +40,7 @@ "use strict";

exports.encryptProvisionMessage = encryptProvisionMessage;
function encryptAttachment(cleartext) {
const aesKey = crypto_1.default.randomBytes(32);
const macKey = crypto_1.default.randomBytes(32);
const iv = crypto_1.default.randomBytes(16);
function encryptAttachment(cleartext, { aesKey, macKey, iv } = {
aesKey: crypto_1.default.randomBytes(32),
macKey: crypto_1.default.randomBytes(32),
iv: crypto_1.default.randomBytes(16),
}) {
const cipher = crypto_1.default.createCipheriv('aes-256-cbc', aesKey, iv);

@@ -46,0 +47,0 @@ const ciphertext = buffer_1.Buffer.concat([cipher.update(cleartext), cipher.final()]);

/// <reference types="node" />
import { ProtocolAddress, PublicKey } from '@signalapp/libsignal-client';
import { ProfileKeyCommitment } from '@signalapp/libsignal-client/zkgroup';
import { BackupLevel, ProfileKeyCommitment } from '@signalapp/libsignal-client/zkgroup';
import { AciString, DeviceId, KyberPreKey, PniString, PreKey, RegistrationId, ServiceIdKind, ServiceIdString, SignedPreKey } from '../types';

@@ -41,2 +41,3 @@ export type DeviceOptions = Readonly<{

};
backupLevel: BackupLevel;
accessKey?: Buffer;

@@ -43,0 +44,0 @@ profileKeyCommitment?: ProfileKeyCommitment;

@@ -11,2 +11,3 @@ "use strict";

const libsignal_client_1 = require("@signalapp/libsignal-client");
const zkgroup_1 = require("@signalapp/libsignal-client/zkgroup");
const types_1 = require("../types");

@@ -21,2 +22,3 @@ const debug = (0, debug_1.default)('mock:device');

capabilities;
backupLevel = zkgroup_1.BackupLevel.Media;
accessKey;

@@ -23,0 +25,0 @@ profileKeyCommitment;

@@ -463,2 +463,66 @@ import z from 'zod';

export type SetBackupKey = z.infer<typeof SetBackupKeySchema>;
export declare const BackupMediaBatchSchema: z.ZodObject<{
items: z.ZodArray<z.ZodObject<{
sourceAttachment: z.ZodObject<{
cdn: z.ZodNumber;
key: z.ZodString;
}, "strip", z.ZodTypeAny, {
cdn: number;
key: string;
}, {
cdn: number;
key: string;
}>;
objectLength: z.ZodNumber;
mediaId: z.ZodString;
hmacKey: z.ZodEffects<z.ZodString, Buffer, string>;
encryptionKey: z.ZodEffects<z.ZodString, Buffer, string>;
iv: z.ZodEffects<z.ZodString, Buffer, string>;
}, "strip", z.ZodTypeAny, {
sourceAttachment: {
cdn: number;
key: string;
};
objectLength: number;
mediaId: string;
hmacKey: Buffer;
encryptionKey: Buffer;
iv: Buffer;
}, {
sourceAttachment: {
cdn: number;
key: string;
};
objectLength: number;
mediaId: string;
hmacKey: string;
encryptionKey: string;
iv: string;
}>, "many">;
}, "strip", z.ZodTypeAny, {
items: {
sourceAttachment: {
cdn: number;
key: string;
};
objectLength: number;
mediaId: string;
hmacKey: Buffer;
encryptionKey: Buffer;
iv: Buffer;
}[];
}, {
items: {
sourceAttachment: {
cdn: number;
key: string;
};
objectLength: number;
mediaId: string;
hmacKey: string;
encryptionKey: string;
iv: string;
}[];
}>;
export type BackupMediaBatch = z.infer<typeof BackupMediaBatchSchema>;
export {};

@@ -8,3 +8,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.SetBackupKeySchema = exports.BackupHeadersSchema = exports.SetBackupIdSchema = exports.DeleteCallLinkSchema = exports.UpdateCallLinkSchema = exports.CreateCallLinkSchema = exports.CreateCallLinkAuthSchema = exports.PutUsernameLinkSchema = exports.UsernameConfirmationSchema = exports.UsernameReservationSchema = exports.GroupStateSchema = exports.AtomicLinkingDataSchema = exports.MessageListSchema = exports.MessageSchema = exports.DeviceKeysSchema = exports.DeviceIdSchema = exports.RegistrationIdSchema = exports.ServiceIdSchema = exports.PniSchema = exports.AciSchema = exports.PositiveInt = void 0;
exports.BackupMediaBatchSchema = exports.SetBackupKeySchema = exports.BackupHeadersSchema = exports.SetBackupIdSchema = exports.DeleteCallLinkSchema = exports.UpdateCallLinkSchema = exports.CreateCallLinkSchema = exports.CreateCallLinkAuthSchema = exports.PutUsernameLinkSchema = exports.UsernameConfirmationSchema = exports.UsernameReservationSchema = exports.GroupStateSchema = exports.AtomicLinkingDataSchema = exports.MessageListSchema = exports.MessageSchema = exports.DeviceKeysSchema = exports.DeviceIdSchema = exports.RegistrationIdSchema = exports.ServiceIdSchema = exports.PniSchema = exports.AciSchema = exports.PositiveInt = void 0;
const zod_1 = __importDefault(require("zod"));

@@ -116,1 +116,16 @@ const util_1 = require("../util");

});
exports.BackupMediaBatchSchema = zod_1.default.object({
items: zod_1.default
.object({
sourceAttachment: zod_1.default.object({
cdn: zod_1.default.number(),
key: zod_1.default.string(),
}),
objectLength: zod_1.default.number(),
mediaId: zod_1.default.string(),
hmacKey: zod_1.default.string().transform(util_1.fromBase64),
encryptionKey: zod_1.default.string().transform(util_1.fromBase64),
iv: zod_1.default.string().transform(util_1.fromBase64),
})
.array(),
});

@@ -12,3 +12,3 @@ /// <reference types="node" />

import { ChangeNumberOptions, Device, DeviceKeys } from '../data/device';
import { BackupHeaders, CreateCallLink, DeleteCallLink, Message, SetBackupId, SetBackupKey, UpdateCallLink, UsernameConfirmation, UsernameReservation } from '../data/schemas';
import { BackupHeaders, BackupMediaBatch, CreateCallLink, DeleteCallLink, Message, SetBackupId, SetBackupKey, UpdateCallLink, UsernameConfirmation, UsernameReservation } from '../data/schemas';
import { AciString, AttachmentId, DeviceId, PniString, ProvisionIdString, ProvisioningCode, RegistrationId, ServiceIdKind, ServiceIdString } from '../types';

@@ -131,2 +131,30 @@ import { ModifyGroupResult, ServerGroup } from './group';

}>;
export type BackupMediaObject = Readonly<{
cdn: 3;
mediaId: string;
objectLength: number;
}>;
export type BackupMediaList = Readonly<{
storedMediaObjects: ReadonlyArray<BackupMediaObject>;
backupDir: string;
mediaDir: string;
cursor: string | undefined;
}>;
export type BackupMediaCursor = {
readonly backupId: string;
remainingMedia: ReadonlyArray<BackupMediaObject>;
};
export type ListBackupMediaOptions = Readonly<{
cursor: string | undefined;
limit: number;
}>;
export type BackupMediaBatchResponse = Readonly<{
status: number;
failureReason?: string;
cdn: 3;
mediaId: string;
}>;
export type BackupMediaBatchResult = Readonly<{
responses: ReadonlyArray<BackupMediaBatchResponse>;
}>;
export type AttachmentUploadForm = Readonly<{

@@ -164,2 +192,4 @@ cdn: 3;

private readonly backupCDNPasswordById;
private readonly backupMediaById;
private readonly backupMediaCursorById;
protected privCertificate: ServerCertificate | undefined;

@@ -236,6 +266,11 @@ protected privZKSecret: ServerSecretParams | undefined;

getBackupInfo(headers: BackupHeaders): Promise<BackupInfo>;
listBackupMedia(headers: BackupHeaders, { cursor, limit }: ListBackupMediaOptions): Promise<BackupMediaList>;
getBackupMediaUploadForm(headers: BackupHeaders): Promise<AttachmentUploadForm>;
getBackupUploadForm(headers: BackupHeaders): Promise<AttachmentUploadForm>;
backupMediaBatch(headers: BackupHeaders, batch: BackupMediaBatch): Promise<BackupMediaBatchResult>;
getBackupCDNAuth(headers: BackupHeaders): Promise<Record<string, string>>;
authorizeBackupCDN(backupId: string, password: string): Promise<boolean>;
getBackupCredentials({ aci }: Device, range: CredentialsRange): Promise<Credentials | undefined>;
getBackupCredentials({ aci, backupLevel }: Device, range: CredentialsRange): Promise<Credentials | undefined>;
protected onNewBackupMediaObject(backupId: string, media: BackupMediaObject): Promise<void>;
protected abstract backupTransitAttachments(backupId: string, batch: BackupMediaBatch): Promise<Array<BackupMediaBatchResponse>>;
abstract isUnregistered(serviceId: ServiceIdString): boolean;

@@ -242,0 +277,0 @@ abstract isSendRateLimited(options: IsSendRateLimitedOptions): boolean;

@@ -58,2 +58,4 @@ "use strict";

backupCDNPasswordById = new Map();
backupMediaById = new Map();
backupMediaCursorById = new Map();
privCertificate;

@@ -808,6 +810,46 @@ privZKSecret;

backupDir: backupId,
mediaDir: `media_${backupId}`,
mediaDir: 'media',
backupName: 'backup',
};
}
async listBackupMedia(headers, { cursor, limit }) {
const backupId = this.authenticateBackup(headers);
let cursorData;
let newCursor;
if (cursor !== undefined) {
cursorData = this.backupMediaCursorById.get(cursor);
}
if (cursorData === undefined) {
newCursor = crypto_1.default.randomBytes(8).toString('hex');
cursorData = {
backupId,
remainingMedia: this.backupMediaById.get(backupId)?.slice() ?? [],
};
this.backupMediaCursorById.set(newCursor, cursorData);
}
else {
assert_1.default.strictEqual(cursorData.backupId, backupId);
}
const storedMediaObjects = cursorData.remainingMedia.slice(0, limit);
// End of list
if (storedMediaObjects.length < limit) {
(0, assert_1.default)(newCursor !== undefined);
this.backupMediaCursorById.delete(newCursor);
newCursor = undefined;
}
else {
cursorData.remainingMedia = cursorData.remainingMedia.slice(limit);
}
return {
storedMediaObjects,
backupDir: backupId,
mediaDir: 'media',
cursor: newCursor,
};
}
async getBackupMediaUploadForm(headers) {
const backupId = this.authenticateBackup(headers);
const form = await this.getAttachmentUploadForm('backups', `transit_${backupId}/${(0, uuid_1.v4)()}`);
return form;
}
async getBackupUploadForm(headers) {

@@ -818,2 +860,7 @@ const backupId = this.authenticateBackup(headers);

}
async backupMediaBatch(headers, batch) {
const backupId = this.authenticateBackup(headers);
const responses = await this.backupTransitAttachments(backupId, batch);
return { responses };
}
async getBackupCDNAuth(headers) {

@@ -839,3 +886,3 @@ const backupId = this.authenticateBackup(headers);

}
async getBackupCredentials({ aci }, range) {
async getBackupCredentials({ aci, backupLevel }, range) {
const req = this.backupAuthReqByAci.get(aci);

@@ -846,7 +893,13 @@ if (req === undefined) {

return this.issueCredentials(range, (redemptionTime) => {
return req.issueCredential(redemptionTime,
// TODO(indutny): offer different levels
zkgroup_1.BackupLevel.Messages, this.backupServerSecret);
return req.issueCredential(redemptionTime, backupLevel, this.backupServerSecret);
});
}
async onNewBackupMediaObject(backupId, media) {
let list = this.backupMediaById.get(backupId);
if (list === undefined) {
list = [];
this.backupMediaById.set(backupId, list);
}
list.push(media);
}
//

@@ -853,0 +906,0 @@ // Private

@@ -369,7 +369,7 @@ "use strict";

});
this.router.get('/v1/archives/auth/read', async (_params, _body, headers, params = {}) => {
this.router.get('/v1/archives/auth/read', async (_params, _body, headers, query = {}) => {
if (this.device) {
return [400, { error: 'Extraneous authentication' }];
}
if (params.cdn !== '3') {
if (query.cdn !== '3') {
return [400, { error: 'Invalid cdn query param' }];

@@ -393,2 +393,41 @@ }

});
this.router.get('/v1/archives/media', async (_params, _body, headers, query = {}) => {
if (this.device) {
return [400, { error: 'Extraneous authentication' }];
}
if (typeof query.limit !== 'string') {
return [400, { error: 'Missing limit param' }];
}
const limit = parseInt(query.limit, 10);
if (limit <= 0) {
return [400, { error: 'Invalid limit' }];
}
const cursor = query.cursor;
return [
200,
await this.server.listBackupMedia(schemas_1.BackupHeadersSchema.parse(headers), { cursor: cursor ? String(cursor) : undefined, limit }),
];
});
this.router.get('/v1/archives/media/upload/form', async (_params, _body, headers) => {
if (this.device) {
return [400, { error: 'Extraneous authentication' }];
}
return [
200,
await this.server.getBackupMediaUploadForm(schemas_1.BackupHeadersSchema.parse(headers)),
];
});
this.router.put('/v1/archives/media/batch', async (_params, body, headers) => {
if (this.device) {
return [400, { error: 'Extraneous authentication' }];
}
if (!body) {
return [400, { error: 'Missing body' }];
}
const batch = schemas_1.BackupMediaBatchSchema.parse(JSON.parse(body.toString()));
return [
200,
await this.server.backupMediaBatch(schemas_1.BackupHeadersSchema.parse(headers), batch),
];
});
//

@@ -395,0 +434,0 @@ // Keepalive

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc