Switch to the /v2/ storage-service endpoints for group operations

This commit is contained in:
Jamie Kyle 2024-05-03 17:42:11 -07:00 committed by GitHub
parent 711f7d3352
commit 408444352f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 68 additions and 36 deletions

View file

@ -211,7 +211,7 @@
"@formatjs/intl": "2.6.7", "@formatjs/intl": "2.6.7",
"@indutny/rezip-electron": "1.3.1", "@indutny/rezip-electron": "1.3.1",
"@mixer/parallel-prettier": "2.0.3", "@mixer/parallel-prettier": "2.0.3",
"@signalapp/mock-server": "6.3.0", "@signalapp/mock-server": "6.4.1",
"@storybook/addon-a11y": "7.4.5", "@storybook/addon-a11y": "7.4.5",
"@storybook/addon-actions": "7.4.5", "@storybook/addon-actions": "7.4.5",
"@storybook/addon-controls": "7.4.5", "@storybook/addon-controls": "7.4.5",

View file

@ -220,6 +220,19 @@ message GroupChange {
uint32 changeEpoch = 3; // Allows clients to decide whether their change logic can successfully apply this diff uint32 changeEpoch = 3; // Allows clients to decide whether their change logic can successfully apply this diff
} }
// External credentials
message ExternalGroupCredential {
string token = 1;
}
// API responses
message GroupResponse {
Group group = 1;
bytes groupSendEndorsementResponse = 2;
}
message GroupChanges { message GroupChanges {
message GroupChangeState { message GroupChangeState {
GroupChange groupChange = 1; GroupChange groupChange = 1;
@ -227,6 +240,12 @@ message GroupChanges {
} }
repeated GroupChangeState groupChanges = 1; repeated GroupChangeState groupChanges = 1;
bytes groupSendEndorsementResponse = 2;
}
message GroupChangeResponse {
GroupChange groupChange = 1;
bytes groupSendEndorsementResponse = 2;
} }
message GroupAttributeBlob { message GroupAttributeBlob {
@ -238,10 +257,6 @@ message GroupAttributeBlob {
} }
} }
message GroupExternalCredential {
string token = 1;
}
message GroupInviteLink { message GroupInviteLink {
message GroupInviteLinkContentsV1 { message GroupInviteLinkContentsV1 {
bytes groupMasterKey = 1; bytes groupMasterKey = 1;

View file

@ -1446,7 +1446,7 @@ async function uploadGroupChange({
actions: Proto.GroupChange.IActions; actions: Proto.GroupChange.IActions;
group: ConversationAttributesType; group: ConversationAttributesType;
inviteLinkPassword?: string; inviteLinkPassword?: string;
}): Promise<Proto.IGroupChange> { }): Promise<Proto.IGroupChangeResponse> {
const logId = idForLogging(group.groupId); const logId = idForLogging(group.groupId);
// Ensure we have the credentials we need before attempting GroupsV2 operations // Ensure we have the credentials we need before attempting GroupsV2 operations
@ -1552,11 +1552,13 @@ export async function modifyGroupV2({
} }
// Upload. If we don't have permission, the server will return an error here. // Upload. If we don't have permission, the server will return an error here.
const groupChange = await uploadGroupChange({ const groupChangeResponse = await uploadGroupChange({
actions, actions,
inviteLinkPassword, inviteLinkPassword,
group: conversation.attributes, group: conversation.attributes,
}); });
const { groupChange } = groupChangeResponse;
strictAssert(groupChange, 'missing groupChange');
const groupChangeBuffer = const groupChangeBuffer =
Proto.GroupChange.encode(groupChange).finish(); Proto.GroupChange.encode(groupChange).finish();
@ -2738,12 +2740,13 @@ export async function respondToGroupV2Migration({
`respondToGroupV2Migration/${logId}: Failed to access log endpoint; fetching full group state` `respondToGroupV2Migration/${logId}: Failed to access log endpoint; fetching full group state`
); );
try { try {
firstGroupState = await makeRequestWithCredentials({ const groupResponse = await makeRequestWithCredentials({
logId: `getGroup/${logId}`, logId: `getGroup/${logId}`,
publicParams, publicParams,
secretParams, secretParams,
request: (sender, options) => sender.getGroup(options), request: (sender, options) => sender.getGroup(options),
}); });
firstGroupState = groupResponse.group;
} catch (secondError) { } catch (secondError) {
if (secondError.code === GROUP_ACCESS_DENIED_CODE) { if (secondError.code === GROUP_ACCESS_DENIED_CODE) {
log.info( log.info(
@ -3652,13 +3655,16 @@ async function updateGroupViaState({
throw new Error('updateGroupViaState: group was missing publicParams!'); throw new Error('updateGroupViaState: group was missing publicParams!');
} }
const groupState = await makeRequestWithCredentials({ const groupResponse = await makeRequestWithCredentials({
logId: `getGroup/${logId}`, logId: `getGroup/${logId}`,
publicParams, publicParams,
secretParams, secretParams,
request: (sender, requestOptions) => sender.getGroup(requestOptions), request: (sender, requestOptions) => sender.getGroup(requestOptions),
}); });
const groupState = groupResponse.group;
strictAssert(groupState, 'Group state must be present');
const decryptedGroupState = decryptGroupState( const decryptedGroupState = decryptGroupState(
groupState, groupState,
secretParams, secretParams,
@ -3792,7 +3798,7 @@ async function updateGroupViaLogs({
// `integrateGroupChanges`. // `integrateGroupChanges`.
let revisionToFetch = isNumber(currentRevision) ? currentRevision : undefined; let revisionToFetch = isNumber(currentRevision) ? currentRevision : undefined;
let response; let response: GroupLogResponseType;
const changes: Array<Proto.IGroupChanges> = []; const changes: Array<Proto.IGroupChanges> = [];
do { do {
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop

View file

@ -2153,7 +2153,7 @@ export default class MessageSender {
async createGroup( async createGroup(
group: Readonly<Proto.IGroup>, group: Readonly<Proto.IGroup>,
options: Readonly<GroupCredentialsType> options: Readonly<GroupCredentialsType>
): Promise<void> { ): Promise<Proto.IGroupResponse> {
return this.server.createGroup(group, options); return this.server.createGroup(group, options);
} }
@ -2166,7 +2166,7 @@ export default class MessageSender {
async getGroup( async getGroup(
options: Readonly<GroupCredentialsType> options: Readonly<GroupCredentialsType>
): Promise<Proto.Group> { ): Promise<Proto.IGroupResponse> {
return this.server.getGroup(options); return this.server.getGroup(options);
} }
@ -2192,7 +2192,7 @@ export default class MessageSender {
changes: Readonly<Proto.GroupChange.IActions>, changes: Readonly<Proto.GroupChange.IActions>,
options: Readonly<GroupCredentialsType>, options: Readonly<GroupCredentialsType>,
inviteLinkBase64?: string inviteLinkBase64?: string
): Promise<Proto.IGroupChange> { ): Promise<Proto.IGroupChangeResponse> {
return this.server.modifyGroup(changes, options, inviteLinkBase64); return this.server.modifyGroup(changes, options, inviteLinkBase64);
} }
@ -2243,8 +2243,8 @@ export default class MessageSender {
async getGroupMembershipToken( async getGroupMembershipToken(
options: Readonly<GroupCredentialsType> options: Readonly<GroupCredentialsType>
): Promise<Proto.GroupExternalCredential> { ): Promise<Proto.IExternalGroupCredential> {
return this.server.getGroupExternalCredential(options); return this.server.getExternalGroupCredential(options);
} }
public async sendChallengeResponse( public async sendChallengeResponse(

View file

@ -538,9 +538,9 @@ const URL_CALLS = {
getBackupCDNCredentials: 'v1/archives/auth/read', getBackupCDNCredentials: 'v1/archives/auth/read',
getBackupUploadForm: 'v1/archives/upload/form', getBackupUploadForm: 'v1/archives/upload/form',
getBackupMediaUploadForm: 'v1/archives/media/upload/form', getBackupMediaUploadForm: 'v1/archives/media/upload/form',
groupLog: 'v1/groups/logs', groupLog: 'v2/groups/logs',
groupJoinedAtVersion: 'v1/groups/joined_at_version', groupJoinedAtVersion: 'v1/groups/joined_at_version',
groups: 'v1/groups', groups: 'v2/groups',
groupsViaLink: 'v1/groups/join/', groupsViaLink: 'v1/groups/join/',
groupToken: 'v1/groups/token', groupToken: 'v1/groups/token',
keys: 'v2/keys', keys: 'v2/keys',
@ -715,6 +715,7 @@ export type GroupLogResponseType = {
start?: number; start?: number;
end?: number; end?: number;
changes: Proto.GroupChanges; changes: Proto.GroupChanges;
groupSendEndorsementResponse: Uint8Array | null;
}; };
export type ProfileRequestDataType = { export type ProfileRequestDataType = {
@ -1144,7 +1145,7 @@ export type WebAPIType = {
createGroup: ( createGroup: (
group: Proto.IGroup, group: Proto.IGroup,
options: GroupCredentialsType options: GroupCredentialsType
) => Promise<void>; ) => Promise<Proto.IGroupResponse>;
deleteUsername: (abortSignal?: AbortSignal) => Promise<void>; deleteUsername: (abortSignal?: AbortSignal) => Promise<void>;
downloadOnboardingStories: ( downloadOnboardingStories: (
version: string, version: string,
@ -1172,7 +1173,7 @@ export type WebAPIType = {
}) => Promise<Readable>; }) => Promise<Readable>;
getAvatar: (path: string) => Promise<Uint8Array>; getAvatar: (path: string) => Promise<Uint8Array>;
getHasSubscription: (subscriberId: Uint8Array) => Promise<boolean>; getHasSubscription: (subscriberId: Uint8Array) => Promise<boolean>;
getGroup: (options: GroupCredentialsType) => Promise<Proto.Group>; getGroup: (options: GroupCredentialsType) => Promise<Proto.IGroupResponse>;
getGroupFromLink: ( getGroupFromLink: (
inviteLinkPassword: string | undefined, inviteLinkPassword: string | undefined,
auth: GroupCredentialsType auth: GroupCredentialsType
@ -1181,9 +1182,9 @@ export type WebAPIType = {
getGroupCredentials: ( getGroupCredentials: (
options: GetGroupCredentialsOptionsType options: GetGroupCredentialsOptionsType
) => Promise<GetGroupCredentialsResultType>; ) => Promise<GetGroupCredentialsResultType>;
getGroupExternalCredential: ( getExternalGroupCredential: (
options: GroupCredentialsType options: GroupCredentialsType
) => Promise<Proto.GroupExternalCredential>; ) => Promise<Proto.IExternalGroupCredential>;
getGroupLog: ( getGroupLog: (
options: GetGroupLogOptionsType, options: GetGroupLogOptionsType,
credentials: GroupCredentialsType credentials: GroupCredentialsType
@ -1253,7 +1254,7 @@ export type WebAPIType = {
changes: Proto.GroupChange.IActions, changes: Proto.GroupChange.IActions,
options: GroupCredentialsType, options: GroupCredentialsType,
inviteLinkBase64?: string inviteLinkBase64?: string
) => Promise<Proto.IGroupChange>; ) => Promise<Proto.IGroupChangeResponse>;
modifyStorageRecords: MessageSender['modifyStorageRecords']; modifyStorageRecords: MessageSender['modifyStorageRecords'];
postBatchIdentityCheck: ( postBatchIdentityCheck: (
elements: VerifyServiceIdRequestType elements: VerifyServiceIdRequestType
@ -1662,7 +1663,7 @@ export function initialize({
getGroup, getGroup,
getGroupAvatar, getGroupAvatar,
getGroupCredentials, getGroupCredentials,
getGroupExternalCredential, getExternalGroupCredential,
getGroupFromLink, getGroupFromLink,
getGroupLog, getGroupLog,
getHasSubscription, getHasSubscription,
@ -3605,9 +3606,9 @@ export function initialize({
return response; return response;
} }
async function getGroupExternalCredential( async function getExternalGroupCredential(
options: GroupCredentialsType options: GroupCredentialsType
): Promise<Proto.GroupExternalCredential> { ): Promise<Proto.IExternalGroupCredential> {
const basicAuth = generateGroupAuth( const basicAuth = generateGroupAuth(
options.groupPublicParamsHex, options.groupPublicParamsHex,
options.authCredentialPresentationHex options.authCredentialPresentationHex
@ -3623,7 +3624,7 @@ export function initialize({
disableSessionResumption: true, disableSessionResumption: true,
}); });
return Proto.GroupExternalCredential.decode(response); return Proto.ExternalGroupCredential.decode(response);
} }
function verifyAttributes(attributes: Proto.IAvatarUploadAttributes) { function verifyAttributes(attributes: Proto.IAvatarUploadAttributes) {
@ -3727,14 +3728,14 @@ export function initialize({
async function createGroup( async function createGroup(
group: Proto.IGroup, group: Proto.IGroup,
options: GroupCredentialsType options: GroupCredentialsType
): Promise<void> { ): Promise<Proto.IGroupResponse> {
const basicAuth = generateGroupAuth( const basicAuth = generateGroupAuth(
options.groupPublicParamsHex, options.groupPublicParamsHex,
options.authCredentialPresentationHex options.authCredentialPresentationHex
); );
const data = Proto.Group.encode(group).finish(); const data = Proto.Group.encode(group).finish();
await _ajax({ const response = await _ajax({
basicAuth, basicAuth,
call: 'groups', call: 'groups',
contentType: 'application/x-protobuf', contentType: 'application/x-protobuf',
@ -3742,12 +3743,15 @@ export function initialize({
host: storageUrl, host: storageUrl,
disableSessionResumption: true, disableSessionResumption: true,
httpType: 'PUT', httpType: 'PUT',
responseType: 'bytes',
}); });
return Proto.GroupResponse.decode(response);
} }
async function getGroup( async function getGroup(
options: GroupCredentialsType options: GroupCredentialsType
): Promise<Proto.Group> { ): Promise<Proto.IGroupResponse> {
const basicAuth = generateGroupAuth( const basicAuth = generateGroupAuth(
options.groupPublicParamsHex, options.groupPublicParamsHex,
options.authCredentialPresentationHex options.authCredentialPresentationHex
@ -3763,7 +3767,7 @@ export function initialize({
responseType: 'bytes', responseType: 'bytes',
}); });
return Proto.Group.decode(response); return Proto.GroupResponse.decode(response);
} }
async function getGroupFromLink( async function getGroupFromLink(
@ -3799,7 +3803,7 @@ export function initialize({
changes: Proto.GroupChange.IActions, changes: Proto.GroupChange.IActions,
options: GroupCredentialsType, options: GroupCredentialsType,
inviteLinkBase64?: string inviteLinkBase64?: string
): Promise<Proto.IGroupChange> { ): Promise<Proto.IGroupChangeResponse> {
const basicAuth = generateGroupAuth( const basicAuth = generateGroupAuth(
options.groupPublicParamsHex, options.groupPublicParamsHex,
options.authCredentialPresentationHex options.authCredentialPresentationHex
@ -3826,7 +3830,7 @@ export function initialize({
: undefined, : undefined,
}); });
return Proto.GroupChange.decode(response); return Proto.GroupChangeResponse.decode(response);
} }
async function getGroupLog( async function getGroupLog(
@ -3876,6 +3880,10 @@ export function initialize({
disableSessionResumption: true, disableSessionResumption: true,
httpType: 'GET', httpType: 'GET',
responseType: 'byteswithdetails', responseType: 'byteswithdetails',
headers: {
// TODO(jamie): To be implmented in DESKTOP-699
'Cached-Send-Endorsements': '0',
},
urlParameters: urlParameters:
`/${startVersion}?` + `/${startVersion}?` +
`includeFirstState=${Boolean(includeFirstState)}&` + `includeFirstState=${Boolean(includeFirstState)}&` +
@ -3884,6 +3892,7 @@ export function initialize({
}); });
const { data, response } = withDetails; const { data, response } = withDetails;
const changes = Proto.GroupChanges.decode(data); const changes = Proto.GroupChanges.decode(data);
const { groupSendEndorsementResponse } = changes;
if (response && response.status === 206) { if (response && response.status === 206) {
const range = response.headers.get('Content-Range'); const range = response.headers.get('Content-Range');
@ -3904,12 +3913,14 @@ export function initialize({
start, start,
end, end,
currentRevision, currentRevision,
groupSendEndorsementResponse,
}; };
} }
} }
return { return {
changes, changes,
groupSendEndorsementResponse,
}; };
} }

View file

@ -4001,10 +4001,10 @@
type-fest "^3.5.0" type-fest "^3.5.0"
uuid "^8.3.0" uuid "^8.3.0"
"@signalapp/mock-server@6.3.0": "@signalapp/mock-server@6.4.1":
version "6.3.0" version "6.4.1"
resolved "https://registry.yarnpkg.com/@signalapp/mock-server/-/mock-server-6.3.0.tgz#5715fc1ff4517310caacc767ab4790530f11c673" resolved "https://registry.yarnpkg.com/@signalapp/mock-server/-/mock-server-6.4.1.tgz#b49700f8d43b0c76d3f02820dd3b3da82a910f12"
integrity sha512-mC4QXqS7+MH1p3U7kTuUqJsFUHfBZ6wemuzvQvhk3+4bRoc77Wynu1uIN0WRLhx/faOGwBkSiAWNiLhQt0Vscw== integrity sha512-is75JwGL2CjLJ3NakMxw6rkgx379aKc3n328lSaiwLKVgBpuG/ms8wF3fNALxFstKoMl41lPzooOMWeqm+ubVQ==
dependencies: dependencies:
"@signalapp/libsignal-client" "^0.42.0" "@signalapp/libsignal-client" "^0.42.0"
debug "^4.3.2" debug "^4.3.2"