diff --git a/services/identity/src/database.rs b/services/identity/src/database.rs --- a/services/identity/src/database.rs +++ b/services/identity/src/database.rs @@ -57,7 +57,7 @@ pub use grpc_clients::identity::DeviceType; mod device_list; -pub use device_list::{DeviceListRow, DeviceRow}; +pub use device_list::{DeviceListRow, DeviceListUpdate, DeviceRow}; use self::device_list::Prekey; diff --git a/services/identity/src/database/device_list.rs b/services/identity/src/database/device_list.rs --- a/services/identity/src/database/device_list.rs +++ b/services/identity/src/database/device_list.rs @@ -64,6 +64,13 @@ pub prekey_signature: String, } +/// A struct representing device list update request +/// payload; issued by the primary device +#[derive(serde::Deserialize)] +pub struct DeviceListUpdate { + devices: Vec, +} + impl DeviceRow { pub fn from_device_key_upload( user_id: impl Into, diff --git a/services/identity/src/grpc_services/authenticated.rs b/services/identity/src/grpc_services/authenticated.rs --- a/services/identity/src/grpc_services/authenticated.rs +++ b/services/identity/src/grpc_services/authenticated.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use crate::config::CONFIG; -use crate::database::DeviceListRow; +use crate::database::{DeviceListRow, DeviceListUpdate}; use crate::ddb_utils::Identifier; use crate::{ client_service::{ @@ -26,6 +26,7 @@ InboundKeyInfo, InboundKeysForUserRequest, InboundKeysForUserResponse, KeyserverKeysResponse, OutboundKeyInfo, OutboundKeysForUserRequest, OutboundKeysForUserResponse, RefreshUserPrekeysRequest, + UpdateDeviceListRequest, UpdateDeviceListResponse, UpdateUserPasswordFinishRequest, UpdateUserPasswordStartRequest, UpdateUserPasswordStartResponse, UploadOneTimeKeysRequest, }; @@ -412,6 +413,13 @@ device_list_updates: stringified_updates, })) } + + async fn update_device_list_for_user( + &self, + _request: tonic::Request, + ) -> Result, tonic::Status> { + Err(tonic::Status::unimplemented("not implemented")) + } } // raw device list that can be serialized to JSON (and then signed in the future) @@ -451,6 +459,31 @@ } } +impl TryFrom for DeviceListUpdate { + type Error = tonic::Status; + fn try_from(request: UpdateDeviceListRequest) -> Result { + serde_json::from_str(&request.new_device_list).map_err(|err| { + error!("Failed to deserialize device list update: {}", err); + tonic::Status::failed_precondition("unexpected error") + }) + } +} + +impl TryFrom for UpdateDeviceListResponse { + type Error = tonic::Status; + fn try_from(signed_list: SignedDeviceList) -> Result { + let stringified_response_payload = serde_json::to_string(&signed_list) + .map_err(|err| { + error!("Failed to serialize device list response: {}", err); + tonic::Status::failed_precondition("unexpected error") + })?; + let response = UpdateDeviceListResponse { + signed_device_list: stringified_response_payload, + }; + Ok(response) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/shared/protos/identity_auth.proto b/shared/protos/identity_auth.proto --- a/shared/protos/identity_auth.proto +++ b/shared/protos/identity_auth.proto @@ -57,6 +57,9 @@ // Returns device list history rpc GetDeviceListForUser(GetDeviceListRequest) returns (GetDeviceListResponse) {} + + rpc UpdateDeviceListForUser(UpdateDeviceListRequest) returns + (UpdateDeviceListResponse) {} } // Helper types @@ -197,3 +200,24 @@ // } repeated string device_list_updates = 1; } + +// UpdateDeviceListForUser + +message UpdateDeviceListRequest { + // A stringified JSON object of the following format: + // { + // "devices": [, ...] + // } + string new_device_list = 1; +} + +message UpdateDeviceListResponse { + // A stringified JSON object of the following format: + // { + // "rawDeviceList": JSON.stringify({ + // "devices": [, ...] + // "timestamp": , + // }) + // } + string signed_device_list = 1; +} diff --git a/web/protobufs/identity-auth-client.cjs b/web/protobufs/identity-auth-client.cjs --- a/web/protobufs/identity-auth-client.cjs +++ b/web/protobufs/identity-auth-client.cjs @@ -749,5 +749,66 @@ }; +/** + * @const + * @type {!grpc.web.MethodDescriptor< + * !proto.identity.auth.UpdateDeviceListRequest, + * !proto.identity.auth.UpdateDeviceListResponse>} + */ +const methodDescriptor_IdentityClientService_UpdateDeviceListForUser = new grpc.web.MethodDescriptor( + '/identity.auth.IdentityClientService/UpdateDeviceListForUser', + grpc.web.MethodType.UNARY, + proto.identity.auth.UpdateDeviceListRequest, + proto.identity.auth.UpdateDeviceListResponse, + /** + * @param {!proto.identity.auth.UpdateDeviceListRequest} request + * @return {!Uint8Array} + */ + function(request) { + return request.serializeBinary(); + }, + proto.identity.auth.UpdateDeviceListResponse.deserializeBinary +); + + +/** + * @param {!proto.identity.auth.UpdateDeviceListRequest} request The + * request proto + * @param {?Object} metadata User defined + * call metadata + * @param {function(?grpc.web.RpcError, ?proto.identity.auth.UpdateDeviceListResponse)} + * callback The callback function(error, response) + * @return {!grpc.web.ClientReadableStream|undefined} + * The XHR Node Readable Stream + */ +proto.identity.auth.IdentityClientServiceClient.prototype.updateDeviceListForUser = + function(request, metadata, callback) { + return this.client_.rpcCall(this.hostname_ + + '/identity.auth.IdentityClientService/UpdateDeviceListForUser', + request, + metadata || {}, + methodDescriptor_IdentityClientService_UpdateDeviceListForUser, + callback); +}; + + +/** + * @param {!proto.identity.auth.UpdateDeviceListRequest} request The + * request proto + * @param {?Object=} metadata User defined + * call metadata + * @return {!Promise} + * Promise that resolves to the response + */ +proto.identity.auth.IdentityClientServicePromiseClient.prototype.updateDeviceListForUser = + function(request, metadata) { + return this.client_.unaryCall(this.hostname_ + + '/identity.auth.IdentityClientService/UpdateDeviceListForUser', + request, + metadata || {}, + methodDescriptor_IdentityClientService_UpdateDeviceListForUser); +}; + + module.exports = proto.identity.auth; diff --git a/web/protobufs/identity-auth-client.cjs.flow b/web/protobufs/identity-auth-client.cjs.flow --- a/web/protobufs/identity-auth-client.cjs.flow +++ b/web/protobufs/identity-auth-client.cjs.flow @@ -87,6 +87,13 @@ callback: (err: grpcWeb.RpcError, response: identityAuthStructs.GetDeviceListResponse) => void ): grpcWeb.ClientReadableStream; + + updateDeviceListForUser( + request: identityAuthStructs.UpdateDeviceListRequest, + metadata: grpcWeb.Metadata | void, + callback: (err: grpcWeb.RpcError, + response: identityAuthStructs.UpdateDeviceListResponse) => void + ): grpcWeb.ClientReadableStream; } declare export class IdentityClientServicePromiseClient { @@ -148,4 +155,9 @@ request: identityAuthStructs.GetDeviceListRequest, metadata?: grpcWeb.Metadata ): Promise; + + updateDeviceListForUser( + request: identityAuthStructs.UpdateDeviceListRequest, + metadata?: grpcWeb.Metadata + ): Promise; } diff --git a/web/protobufs/identity-auth-structs.cjs b/web/protobufs/identity-auth-structs.cjs --- a/web/protobufs/identity-auth-structs.cjs +++ b/web/protobufs/identity-auth-structs.cjs @@ -40,6 +40,8 @@ goog.exportSymbol('proto.identity.auth.OutboundKeysForUserRequest', null, global); goog.exportSymbol('proto.identity.auth.OutboundKeysForUserResponse', null, global); goog.exportSymbol('proto.identity.auth.RefreshUserPrekeysRequest', null, global); +goog.exportSymbol('proto.identity.auth.UpdateDeviceListRequest', null, global); +goog.exportSymbol('proto.identity.auth.UpdateDeviceListResponse', null, global); goog.exportSymbol('proto.identity.auth.UpdateUserPasswordFinishRequest', null, global); goog.exportSymbol('proto.identity.auth.UpdateUserPasswordStartRequest', null, global); goog.exportSymbol('proto.identity.auth.UpdateUserPasswordStartResponse', null, global); @@ -422,6 +424,48 @@ */ proto.identity.auth.GetDeviceListResponse.displayName = 'proto.identity.auth.GetDeviceListResponse'; } +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.identity.auth.UpdateDeviceListRequest = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.identity.auth.UpdateDeviceListRequest, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.identity.auth.UpdateDeviceListRequest.displayName = 'proto.identity.auth.UpdateDeviceListRequest'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.identity.auth.UpdateDeviceListResponse = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.identity.auth.UpdateDeviceListResponse, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.identity.auth.UpdateDeviceListResponse.displayName = 'proto.identity.auth.UpdateDeviceListResponse'; +} @@ -3853,4 +3897,264 @@ }; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.identity.auth.UpdateDeviceListRequest.prototype.toObject = function(opt_includeInstance) { + return proto.identity.auth.UpdateDeviceListRequest.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.identity.auth.UpdateDeviceListRequest} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.identity.auth.UpdateDeviceListRequest.toObject = function(includeInstance, msg) { + var f, obj = { + newDeviceList: jspb.Message.getFieldWithDefault(msg, 1, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.identity.auth.UpdateDeviceListRequest} + */ +proto.identity.auth.UpdateDeviceListRequest.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.identity.auth.UpdateDeviceListRequest; + return proto.identity.auth.UpdateDeviceListRequest.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.identity.auth.UpdateDeviceListRequest} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.identity.auth.UpdateDeviceListRequest} + */ +proto.identity.auth.UpdateDeviceListRequest.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setNewDeviceList(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.identity.auth.UpdateDeviceListRequest.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.identity.auth.UpdateDeviceListRequest.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.identity.auth.UpdateDeviceListRequest} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.identity.auth.UpdateDeviceListRequest.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getNewDeviceList(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } +}; + + +/** + * optional string new_device_list = 1; + * @return {string} + */ +proto.identity.auth.UpdateDeviceListRequest.prototype.getNewDeviceList = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.identity.auth.UpdateDeviceListRequest} returns this + */ +proto.identity.auth.UpdateDeviceListRequest.prototype.setNewDeviceList = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.identity.auth.UpdateDeviceListResponse.prototype.toObject = function(opt_includeInstance) { + return proto.identity.auth.UpdateDeviceListResponse.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.identity.auth.UpdateDeviceListResponse} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.identity.auth.UpdateDeviceListResponse.toObject = function(includeInstance, msg) { + var f, obj = { + signedDeviceList: jspb.Message.getFieldWithDefault(msg, 1, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.identity.auth.UpdateDeviceListResponse} + */ +proto.identity.auth.UpdateDeviceListResponse.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.identity.auth.UpdateDeviceListResponse; + return proto.identity.auth.UpdateDeviceListResponse.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.identity.auth.UpdateDeviceListResponse} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.identity.auth.UpdateDeviceListResponse} + */ +proto.identity.auth.UpdateDeviceListResponse.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setSignedDeviceList(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.identity.auth.UpdateDeviceListResponse.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.identity.auth.UpdateDeviceListResponse.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.identity.auth.UpdateDeviceListResponse} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.identity.auth.UpdateDeviceListResponse.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getSignedDeviceList(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } +}; + + +/** + * optional string signed_device_list = 1; + * @return {string} + */ +proto.identity.auth.UpdateDeviceListResponse.prototype.getSignedDeviceList = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.identity.auth.UpdateDeviceListResponse} returns this + */ +proto.identity.auth.UpdateDeviceListResponse.prototype.setSignedDeviceList = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + goog.object.extend(exports, proto.identity.auth); diff --git a/web/protobufs/identity-auth-structs.cjs.flow b/web/protobufs/identity-auth-structs.cjs.flow --- a/web/protobufs/identity-auth-structs.cjs.flow +++ b/web/protobufs/identity-auth-structs.cjs.flow @@ -418,3 +418,35 @@ export type GetDeviceListResponseObject = { deviceListUpdatesList: Array, } + +declare export class UpdateDeviceListRequest extends Message { + getNewDeviceList(): string; + setNewDeviceList(value: string): UpdateDeviceListRequest; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): UpdateDeviceListRequestObject; + static toObject(includeInstance: boolean, msg: UpdateDeviceListRequest): UpdateDeviceListRequestObject; + static serializeBinaryToWriter(message: UpdateDeviceListRequest, writer: BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): UpdateDeviceListRequest; + static deserializeBinaryFromReader(message: UpdateDeviceListRequest, reader: BinaryReader): UpdateDeviceListRequest; +} + +export type UpdateDeviceListRequestObject = { + newDeviceList: string, +} + +declare export class UpdateDeviceListResponse extends Message { + getSignedDeviceList(): string; + setSignedDeviceList(value: string): UpdateDeviceListResponse; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): UpdateDeviceListResponseObject; + static toObject(includeInstance: boolean, msg: UpdateDeviceListResponse): UpdateDeviceListResponseObject; + static serializeBinaryToWriter(message: UpdateDeviceListResponse, writer: BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): UpdateDeviceListResponse; + static deserializeBinaryFromReader(message: UpdateDeviceListResponse, reader: BinaryReader): UpdateDeviceListResponse; +} + +export type UpdateDeviceListResponseObject = { + signedDeviceList: string, +}