diff --git a/services/identity/src/client_service.rs b/services/identity/src/client_service.rs --- a/services/identity/src/client_service.rs +++ b/services/identity/src/client_service.rs @@ -696,6 +696,13 @@ self.verify_and_remove_nonce(&nonce).await?; + let user_identifier = self + .client + .get_user_identifier(&user_id) + .await + .map_err(handle_db_error)? + .ok_or_else(|| tonic::Status::not_found("user not found"))?; + let Some(device_list) = self .client .get_current_device_list(&user_id) @@ -713,12 +720,6 @@ } let login_time = chrono::Utc::now(); - - let user_identifier = self - .client - .get_user_identifier(&user_id) - .await - .map_err(handle_db_error)?; let token = AccessTokenData::with_created_time( user_id.clone(), device_id, 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 @@ -974,20 +974,22 @@ .map_err(|e| Error::AwsSdk(e.into())) } + /// Retrieves username for password users or wallet address for wallet users + /// Returns `None` if user not found pub async fn get_user_identifier( &self, user_id: &str, - ) -> Result { - let user_info = self + ) -> Result, Error> { + self .get_item_from_users_table(user_id) .await? .item - .ok_or(Error::MissingItem)?; - - Identifier::try_from(user_info).map_err(|e| { - error!(user_id, "Database item is missing an identifier"); - e - }) + .map(Identifier::try_from) + .transpose() + .map_err(|e| { + error!(user_id, "Database item is missing an identifier"); + e + }) } async fn get_all_usernames(&self) -> Result, Error> { 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 @@ -24,6 +24,7 @@ UpdateUserPasswordStartRequest, UpdateUserPasswordStartResponse, UploadOneTimeKeysRequest, }; +use super::protos::auth::{UserIdentityRequest, UserIdentityResponse}; use super::protos::unauth::Empty; #[derive(derive_more::Constructor)] @@ -165,7 +166,8 @@ .db_client .get_user_identifier(user_id) .await - .map_err(handle_db_error)?; + .map_err(handle_db_error)? + .ok_or_else(|| tonic::Status::not_found("user not found"))?; let identity_info = IdentityInfo::try_from(identifier)?; @@ -196,7 +198,8 @@ .db_client .get_user_identifier(&message.user_id) .await - .map_err(handle_db_error)?; + .map_err(handle_db_error)? + .ok_or_else(|| tonic::Status::not_found("user not found"))?; let identity_info = IdentityInfo::try_from(identifier)?; @@ -449,6 +452,29 @@ let response = Empty {}; Ok(Response::new(response)) } + + async fn find_user_identity( + &self, + request: tonic::Request, + ) -> Result, tonic::Status> { + use identity::IdentityInfo; + + let message = request.into_inner(); + let identifier = self + .db_client + .get_user_identifier(&message.user_id) + .await + .map_err(handle_db_error)? + .ok_or_else(|| tonic::Status::not_found("user not found"))?; + + let identity_info = IdentityInfo::try_from(identifier)?; + let identity = Some(Identity { + identity_info: Some(identity_info), + }); + + let response = Response::new(UserIdentityResponse { identity }); + return Ok(response); + } } // raw device list that can be serialized to JSON (and then signed in the future) 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 @@ -66,6 +66,8 @@ // Called by an existing user to unlink their Farcaster account rpc UnlinkFarcasterAccount(identity.unauth.Empty) returns (identity.unauth.Empty) {} + + rpc FindUserIdentity(UserIdentityRequest) returns (UserIdentityResponse) {} } // Helper types @@ -207,3 +209,14 @@ message LinkFarcasterAccountRequest { string farcaster_id = 1; } + +// FindUserIdentity + +message UserIdentityRequest { + // user ID for which we want to get the identity + string user_id = 1; +} + +message UserIdentityResponse { + Identity identity = 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 @@ -871,4 +871,66 @@ }; +/** + * @const + * @type {!grpc.web.MethodDescriptor< + * !proto.identity.auth.UserIdentityRequest, + * !proto.identity.auth.UserIdentityResponse>} + */ +const methodDescriptor_IdentityClientService_FindUserIdentity = new grpc.web.MethodDescriptor( + '/identity.auth.IdentityClientService/FindUserIdentity', + grpc.web.MethodType.UNARY, + proto.identity.auth.UserIdentityRequest, + proto.identity.auth.UserIdentityResponse, + /** + * @param {!proto.identity.auth.UserIdentityRequest} request + * @return {!Uint8Array} + */ + function(request) { + return request.serializeBinary(); + }, + proto.identity.auth.UserIdentityResponse.deserializeBinary +); + + +/** + * @param {!proto.identity.auth.UserIdentityRequest} request The + * request proto + * @param {?Object} metadata User defined + * call metadata + * @param {function(?grpc.web.RpcError, ?proto.identity.auth.UserIdentityResponse)} + * callback The callback function(error, response) + * @return {!grpc.web.ClientReadableStream|undefined} + * The XHR Node Readable Stream + */ +proto.identity.auth.IdentityClientServiceClient.prototype.findUserIdentity = + function(request, metadata, callback) { + return this.client_.rpcCall(this.hostname_ + + '/identity.auth.IdentityClientService/FindUserIdentity', + request, + metadata || {}, + methodDescriptor_IdentityClientService_FindUserIdentity, + callback); +}; + + +/** + * @param {!proto.identity.auth.UserIdentityRequest} request The + * request proto + * @param {?Object=} metadata User defined + * call metadata + * @return {!Promise} + * Promise that resolves to the response + */ +proto.identity.auth.IdentityClientServicePromiseClient.prototype.findUserIdentity = + function(request, metadata) { + return this.client_.unaryCall(this.hostname_ + + '/identity.auth.IdentityClientService/FindUserIdentity', + request, + metadata || {}, + methodDescriptor_IdentityClientService_FindUserIdentity); +}; + + 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 @@ -101,6 +101,13 @@ callback: (err: grpcWeb.RpcError, response: identityStructs.Empty) => void ): grpcWeb.ClientReadableStream; + + findUserIdentity( + request: identityAuthStructs.UserIdentityRequest, + metadata: grpcWeb.Metadata | void, + callback: (err: grpcWeb.RpcError, + response: identityAuthStructs.UserIdentityResponse) => void + ): grpcWeb.ClientReadableStream; } declare export class IdentityClientServicePromiseClient { @@ -172,4 +179,9 @@ request: identityStructs.Empty, metadata?: grpcWeb.Metadata ): Promise; + + findUserIdentity( + request: identityAuthStructs.UserIdentityRequest, + 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 @@ -43,6 +43,8 @@ goog.exportSymbol('proto.identity.auth.UpdateUserPasswordStartRequest', null, global); goog.exportSymbol('proto.identity.auth.UpdateUserPasswordStartResponse', null, global); goog.exportSymbol('proto.identity.auth.UploadOneTimeKeysRequest', null, global); +goog.exportSymbol('proto.identity.auth.UserIdentityRequest', null, global); +goog.exportSymbol('proto.identity.auth.UserIdentityResponse', null, global); /** * Generated by JsPbCodeGenerator. * @param {Array=} opt_data Optional initial data array, typically from a @@ -421,6 +423,48 @@ */ proto.identity.auth.LinkFarcasterAccountRequest.displayName = 'proto.identity.auth.LinkFarcasterAccountRequest'; } +/** + * 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.UserIdentityRequest = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.identity.auth.UserIdentityRequest, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.identity.auth.UserIdentityRequest.displayName = 'proto.identity.auth.UserIdentityRequest'; +} +/** + * 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.UserIdentityResponse = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.identity.auth.UserIdentityResponse, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.identity.auth.UserIdentityResponse.displayName = 'proto.identity.auth.UserIdentityResponse'; +} @@ -3712,4 +3756,285 @@ }; + + + +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.UserIdentityRequest.prototype.toObject = function(opt_includeInstance) { + return proto.identity.auth.UserIdentityRequest.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.UserIdentityRequest} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.identity.auth.UserIdentityRequest.toObject = function(includeInstance, msg) { + var f, obj = { + userId: 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.UserIdentityRequest} + */ +proto.identity.auth.UserIdentityRequest.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.identity.auth.UserIdentityRequest; + return proto.identity.auth.UserIdentityRequest.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.identity.auth.UserIdentityRequest} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.identity.auth.UserIdentityRequest} + */ +proto.identity.auth.UserIdentityRequest.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.setUserId(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.identity.auth.UserIdentityRequest.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.identity.auth.UserIdentityRequest.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.UserIdentityRequest} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.identity.auth.UserIdentityRequest.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getUserId(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } +}; + + +/** + * optional string user_id = 1; + * @return {string} + */ +proto.identity.auth.UserIdentityRequest.prototype.getUserId = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.identity.auth.UserIdentityRequest} returns this + */ +proto.identity.auth.UserIdentityRequest.prototype.setUserId = 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.UserIdentityResponse.prototype.toObject = function(opt_includeInstance) { + return proto.identity.auth.UserIdentityResponse.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.UserIdentityResponse} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.identity.auth.UserIdentityResponse.toObject = function(includeInstance, msg) { + var f, obj = { + identity: (f = msg.getIdentity()) && proto.identity.auth.Identity.toObject(includeInstance, f) + }; + + 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.UserIdentityResponse} + */ +proto.identity.auth.UserIdentityResponse.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.identity.auth.UserIdentityResponse; + return proto.identity.auth.UserIdentityResponse.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.identity.auth.UserIdentityResponse} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.identity.auth.UserIdentityResponse} + */ +proto.identity.auth.UserIdentityResponse.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = new proto.identity.auth.Identity; + reader.readMessage(value,proto.identity.auth.Identity.deserializeBinaryFromReader); + msg.setIdentity(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.identity.auth.UserIdentityResponse.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.identity.auth.UserIdentityResponse.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.UserIdentityResponse} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.identity.auth.UserIdentityResponse.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getIdentity(); + if (f != null) { + writer.writeMessage( + 1, + f, + proto.identity.auth.Identity.serializeBinaryToWriter + ); + } +}; + + +/** + * optional Identity identity = 1; + * @return {?proto.identity.auth.Identity} + */ +proto.identity.auth.UserIdentityResponse.prototype.getIdentity = function() { + return /** @type{?proto.identity.auth.Identity} */ ( + jspb.Message.getWrapperField(this, proto.identity.auth.Identity, 1)); +}; + + +/** + * @param {?proto.identity.auth.Identity|undefined} value + * @return {!proto.identity.auth.UserIdentityResponse} returns this +*/ +proto.identity.auth.UserIdentityResponse.prototype.setIdentity = function(value) { + return jspb.Message.setWrapperField(this, 1, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.identity.auth.UserIdentityResponse} returns this + */ +proto.identity.auth.UserIdentityResponse.prototype.clearIdentity = function() { + return this.setIdentity(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.identity.auth.UserIdentityResponse.prototype.hasIdentity = function() { + return jspb.Message.getField(this, 1) != null; +}; + + 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 @@ -404,3 +404,37 @@ export type LinkFarcasterAccountRequestObject = { farcasterId: string, } + +declare export class UserIdentityRequest extends Message { + getUserId(): string; + setUserId(value: string): UserIdentityRequest; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): UserIdentityRequestObject; + static toObject(includeInstance: boolean, msg: UserIdentityRequest): UserIdentityRequestObject; + static serializeBinaryToWriter(message: UserIdentityRequest, writer: BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): UserIdentityRequest; + static deserializeBinaryFromReader(message: UserIdentityRequest, reader: BinaryReader): UserIdentityRequest; +} + +export type UserIdentityRequestObject = { + userId: string, +} + +declare export class UserIdentityResponse extends Message { + getIdentity(): Identity | void; + setIdentity(value?: Identity): UserIdentityResponse; + hasIdentity(): boolean; + clearIdentity(): UserIdentityResponse; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): UserIdentityResponseObject; + static toObject(includeInstance: boolean, msg: UserIdentityResponse): UserIdentityResponseObject; + static serializeBinaryToWriter(message: UserIdentityResponse, writer: BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): UserIdentityResponse; + static deserializeBinaryFromReader(message: UserIdentityResponse, reader: BinaryReader): UserIdentityResponse; +} + +export type UserIdentityResponseObject = { + identity: ?IdentityObject, +}