예제 #1
0
        /// <summary>
        /// Changes the user password.<para> </para>
        /// Remember that <paramref name="requestBody"/>'s field
        /// <see cref="EpistleRequestBody.Body"/> should be the <see cref="UserChangePasswordRequestDto"/>
        /// instance serialized into JSON and then compressed!
        /// </summary>
        /// <param name="requestBody">Request parameters DTO wrapped into an <see cref="EpistleRequestBody"/>.</param>
        /// <returns><c>bool</c> indicating whether the change was successful or not.</returns>
        public async Task <bool> ChangeUserPassword(EpistleRequestBody requestBody)
        {
            var          request  = EpistleRequest(requestBody, "users/change-pw", Method.Put);
            RestResponse response = await restClient.ExecuteAsync(request).ConfigureAwait(false);

            return(response?.StatusCode == HttpStatusCode.OK);
        }
예제 #2
0
        /// <summary>
        /// Deletes a user irreversibly from the backend's db.<para> </para>
        /// <paramref name="requestBody.Body"/> should directly be the unprocessed <see cref="User.PasswordSHA512"/>.
        /// </summary>
        /// <param name="requestBody">Request parameters DTO wrapped into an <see cref="EpistleRequestBody"/>.</param>
        /// <returns>Whether deletion was successful or not.</returns>
        public async Task <bool> DeleteUser(EpistleRequestBody requestBody)
        {
            var          request  = EpistleRequest(requestBody, "users/delete");
            RestResponse response = await restClient.ExecuteAsync(request).ConfigureAwait(false);

            return(response?.StatusCode == HttpStatusCode.NoContent);
        }
        /// <summary>
        /// Kick a user from a conversation.
        /// </summary>
        /// <param name="requestBody">Request parameters.</param>
        /// <returns>Whether the user was kicked out successfully or not.</returns>
        public async Task <bool> KickUser(EpistleRequestBody requestBody)
        {
            var          request  = EpistleRequest(requestBody, "convos/kick", Method.Put);
            RestResponse response = await restClient.ExecuteAsync(request);

            return(response.IsSuccessful);
        }
        /// <summary>
        /// Creates a new convo on the server.<para> </para>
        /// "<paramref name="requestBody.Body"/>" should be the <see cref="ConvoCreationRequestDto"/> serialized into JSON and compressed.
        /// </summary>
        /// <param name="requestBody">Request body containing the parameters (auth, etc...).</param>
        /// <returns><c>null</c> if creation failed; the created <see cref="Convo"/>'s unique id.</returns>
        public async Task <string> CreateConvo(EpistleRequestBody requestBody)
        {
            var          request  = EpistleRequest(requestBody, "convos/create");
            RestResponse response = await restClient.ExecuteAsync(request).ConfigureAwait(false);

            return(response.IsSuccessful ? response.Content : null);
        }
        /// <summary>
        /// Changes a convo's metadata (description, title, etc...).<para> </para>
        /// The user making the request needs to be the <see cref="Convo"/>'s admin (Creator).<para> </para>
        /// If you're assigning a new admin, he needs to be a participant of the <see cref="Convo"/>, else you'll get a bad request returned from the web api.
        /// </summary>
        /// <param name="requestBody">Request body containing the authentication parameters + the data that needs to be changed (<c>null</c> fields will be ignored; fields with values will be updated and persisted into the server's db)..</param>
        /// <returns>Whether the convo's metadata was changed successfully or not.</returns>
        public async Task <bool> ChangeConvoMetadata(EpistleRequestBody requestBody)
        {
            var          request  = EpistleRequest(requestBody, "convos/metadata", Method.Put);
            RestResponse response = await restClient.ExecuteAsync(request);

            return(response.IsSuccessful);
        }
        /// <summary>
        /// Join a <see cref="Convo" />.
        /// </summary>
        /// <param name="requestBody">Request body containing the convo join parameters.</param>
        /// <returns>Whether the <see cref="Convo" /> was joined successfully or not.</returns>
        public async Task <bool> JoinConvo(EpistleRequestBody requestBody)
        {
            var          request  = EpistleRequest(requestBody, "convos/join", Method.Put);
            RestResponse response = await restClient.ExecuteAsync(request).ConfigureAwait(false);

            return(response.IsSuccessful);
        }
        /// <summary>
        /// Posts a message to a <see cref="Convo" />.
        /// </summary>
        /// <param name="requestBody">Request body containing the message post parameters.</param>
        /// <returns>Whether the message was posted successfully or not.</returns>
        public async Task <bool> PostMessage(EpistleRequestBody requestBody)
        {
            var          request  = EpistleRequest(requestBody, "convos/post");
            RestResponse response = await restClient.ExecuteAsync(request);

            return(response.IsSuccessful);
        }
#pragma warning restore 1591

        /// <summary>
        /// Changes an Epistle <see cref="User"/>'s password.
        /// </summary>
        /// <param name="oldPw">The old <see cref="User"/> password (NOT its SHA512!).</param>
        /// <param name="newPw">The new, better and safer password (NOT its SHA512!).</param>
        /// <param name="totp">Request authentication token.</param>
        /// <returns>Whether the password change request was successful or not.</returns>
        /// <exception cref="ApplicationException">Thrown if the <see cref="User.PrivateKeyPem"/> is <c>null</c> or empty.</exception>
        public async Task <bool> ChangePassword(string oldPw, string newPw, string totp)
        {
            if (oldPw == newPw)
            {
                return(false);
            }

            if (user.PrivateKeyPem.NullOrEmpty())
            {
                string msg = "The user's in-memory private key seems to be null or empty; can't change passwords without re-encrypting a new copy of the user key!";
                logger?.LogError(msg);
                throw new ApplicationException(msg);
            }

            var dto = new UserChangePasswordRequestDto
            {
                Totp          = totp,
                OldPwSHA512   = oldPw.SHA512(),
                NewPwSHA512   = newPw.SHA512(),
                NewPrivateKey = await keyExchange.EncryptAndCompressPrivateKeyAsync(user.PrivateKeyPem, newPw).ConfigureAwait(false)
            };

            var requestBody = new EpistleRequestBody
            {
                UserId = user.Id,
                Auth   = user.Token.Item2,
                Body   = await compressionUtility.Compress(JsonSerializer.Serialize(dto)).ConfigureAwait(false)
            };

            bool success = await userService.ChangeUserPassword(requestBody.Sign(crypto, user.PrivateKeyPem)).ConfigureAwait(false);

            return(success);
        }
예제 #9
0
        /// <summary>
        /// Uploads a new profile picture to the server.
        /// </summary>
        /// <param name="totp">2FA Token.</param>
        /// <param name="pic">The profile picture (base64-encoded). Can be <c>null</c>.</param>
        /// <returns>Whether the update was accepted or failed.</returns>
        public async Task <bool> UpdateUserProfilePicture(string totp, string pic)
        {
            try
            {
                if (user is null || pic.Length > 512 * 512 || user.Id.NullOrEmpty() || user.Token is null || user.Token.Item2.NullOrEmpty() || totp.NullOrEmpty())
                {
                    return(false);
                }

                var dto = new UserChangeProfilePictureRequestDto
                {
                    Totp           = totp,
                    ProfilePicture = pic,
                    PasswordSHA512 = user.PasswordSHA512
                };

                var requestBody = new EpistleRequestBody
                {
                    UserId = user.Id,
                    Auth   = user.Token?.Item2,
                    Body   = JsonSerializer.Serialize(dto)
                };

                var          request  = EpistleRequest(requestBody.Sign(rsa, user.PrivateKeyPem), "users/pic", Method.Put);
                RestResponse response = await restClient.ExecuteAsync(request).ConfigureAwait(false);

                return(response?.StatusCode == HttpStatusCode.OK);
            }
            catch
            {
                return(false);
            }
        }
        private async void TryJoinConvos()
        {
            if (!appSettings["SaveConvoPasswords", true])
            {
                return;
            }

            var userConvos = (await userService.GetConvos(user.Id, user.Token.Item2))
                             .Select(c => (Convo)c)
                             .Distinct()
                             .Where(convo => !convo.IsExpired()).ToArray();

            foreach (var convo in userConvos)
            {
                string cachedPwSHA512 = convoPasswordProvider.GetPasswordSHA512(convo.Id);

                if (cachedPwSHA512.NullOrEmpty())
                {
                    cachedPwSHA512 = await SecureStorage.GetAsync($"convo:{convo.Id}_pw:SHA512");
                }

                if (cachedPwSHA512.NotNullNotEmpty())
                {
                    var dto = new ConvoJoinRequestDto
                    {
                        ConvoId             = convo.Id,
                        ConvoPasswordSHA512 = cachedPwSHA512
                    };

                    var body = new EpistleRequestBody
                    {
                        UserId = user.Id,
                        Auth   = user.Token.Item2,
                        Body   = JsonSerializer.Serialize(dto)
                    };

                    if (await convoService.JoinConvo(body.Sign(crypto, user.PrivateKeyPem)))
                    {
                        convoPasswordProvider.SetPasswordSHA512(convo.Id, cachedPwSHA512);
                        ConvoMetadataDto metadata = await convoService.GetConvoMetadata(convo.Id, cachedPwSHA512, user.Id, user.Token.Item2);

                        if (metadata != null)
                        {
                            var viewModel = viewModelFactory.Create <ActiveConvoViewModel>();
                            viewModel.ActiveConvo = convo;
                            viewModel.OnAppearing();

                            activeConvos[convo.Id] = viewModel;
                        }
                    }
                }
            }

            eventAggregator.GetEvent <TriggerUpdateConvosListEvent>().Publish();
        }
예제 #11
0
        /// <summary>
        /// Creates a <see cref="RestRequest"/> using the provided <paramref name="requestBody"/>, <paramref name="endpoint"/> and <paramref name="method"/>.<para> </para>
        /// The <paramref name="requestBody"/> is serialized to JSON and added to the HTTP request body.<para> </para>
        /// Please only add bodies to POST, and maybe PUT requests...
        /// </summary>
        /// <param name="requestBody">The <see cref="EpistleRequestBody"/> to serialize into JSON and add to the HTTP POST (or PUT) request's body.</param>
        /// <param name="endpoint">The API endpoint (relative path).</param>
        /// <param name="method">The HTTP method to use. Should be either POST or PUT if you use the request body.</param>
        /// <returns>The <see cref="RestRequest"/>, ready to be submitted.</returns>
        protected RestRequest EpistleRequest(EpistleRequestBody requestBody, string endpoint, Method method = Method.Post)
        {
            if (method != Method.Post && method != Method.Put)
            {
                throw new ArgumentException($"{nameof(EpistleWebApiService)}::{nameof(EpistleRequest)}: Non-PUT or POST HTTP method passed. Please only add request bodies to POST and PUT requests!");
            }

            var request = new RestRequest(
                method: method,
                resource: new Uri(endpoint, UriKind.Relative)
                );

            request.AddStringBody(JsonSerializer.Serialize(requestBody), "application/json");

            return(request);
        }
        /// <summary>
        /// Deletes a convo server-side.
        /// </summary>
        /// <param name="requestBody">Request body containing the convo deletion parameters (auth, etc...).</param>
        /// <returns>Whether deletion was successful or not.</returns>
        public async Task <bool> DeleteConvo(EpistleRequestBody requestBody)
        {
            RestResponse response = await restClient.ExecuteAsync(EpistleRequest(requestBody, "convos/delete"));

            return(response.IsSuccessful);
        }
예제 #13
0
        /// <summary>
        /// Submits a message to a <see cref="Convo"/>.
        /// </summary>
        /// <param name="convo">The <see cref="Convo"/> to post the message into.</param>
        /// <param name="messageType">The type of message (e.g. "TEXT=UTF8", "FILE=example.png", etc...).</param>
        /// <param name="message">The message body to encrypt and post.</param>
        /// <returns>Whether the message could be submitted successfully or not.</returns>
        private async Task <bool> PostMessageToConvo(Convo convo, string messageType, byte[] message)
        {
            if (message.NullOrEmpty())
            {
                return(false);
            }

            Task <IDictionary <string, string> > publicKeys = userService.GetUserPublicKeys(user.Id, convo.GetParticipantIdsCommaSeparated(), user.Token.Item2);

            byte[] compressedMessage = await compressionUtility.Compress(message, CompressionSettings.Default).ConfigureAwait(false);

            using var encryptionResult = await aes.EncryptAsync(compressedMessage).ConfigureAwait(false);

            byte[] key = new byte[48];

            for (int i = 0; i < 32; i++)
            {
                key[i] = encryptionResult.Key[i];
            }

            for (int i = 0; i < 16; i++)
            {
                key[i + 32] = encryptionResult.IV[i];
            }

            // Encrypt the message decryption key for every convo participant individually.
            var encryptedKeys = new ConcurrentBag <string>();

            Parallel.ForEach(await publicKeys.ConfigureAwait(false), kvp =>
            {
                (string userId, string userPublicKey) = kvp;

                if (userId.NotNullNotEmpty() && userPublicKey.NotNullNotEmpty())
                {
                    string decompressedPublicKey = keyExchange.DecompressPublicKey(userPublicKey);
                    encryptedKeys.Add(userId + ':' + Convert.ToBase64String(rsa.Encrypt(key, decompressedPublicKey)));
                }
            });

            try
            {
                var postParamsDto = new PostMessageParamsDto
                {
                    Type                = messageType,
                    SenderName          = userSettings.Username,
                    ConvoId             = convo.Id,
                    ConvoPasswordSHA512 = convoPasswordProvider.GetPasswordSHA512(convo.Id),
                    EncryptedKeys       = encryptedKeys.ToCommaSeparatedString(),
                    EncryptedBody       = Convert.ToBase64String(encryptionResult.EncryptedData)
                };

                var body = new EpistleRequestBody
                {
                    UserId = user.Id,
                    Auth   = user.Token.Item2,
                    Body   = JsonSerializer.Serialize(postParamsDto)
                };

                return(await convoService.PostMessage(body.Sign(rsa, user.PrivateKeyPem)).ConfigureAwait(false));
            }
            catch
            {
                return(false);
            }
        }