Changing Password is functional through API and Client

This commit is contained in:
Daniel Schick 2023-08-18 15:29:20 +02:00
parent 083ea69961
commit 411ea8135e
11 changed files with 1208 additions and 678 deletions

View File

@ -1,7 +1,7 @@
//----------------------
// <auto-generated>
// Generated REST API Client Code Generator v1.7.17.0 on 17.08.2023 10:11:31
// Generated REST API Client Code Generator v1.7.17.0 on 18.08.2023 15:15:15
// Using the tool OpenAPI Generator v6.6.0
// </auto-generated>
//----------------------
@ -280,6 +280,25 @@ namespace BreCalClient.misc.Api
/// <param name="operationIndex">Index associated with the operation.</param>
/// <returns>ApiResponse of Object(void)</returns>
ApiResponse<Object> TimesPutWithHttpInfo(Times times, int operationIndex = 0);
/// <summary>
/// Update user details (first/last name, phone, password)
/// </summary>
/// <exception cref="BreCalClient.misc.Client.ApiException">Thrown when fails to make API call</exception>
/// <param name="userDetails">User details</param>
/// <param name="operationIndex">Index associated with the operation.</param>
/// <returns></returns>
void UserPut(UserDetails userDetails, int operationIndex = 0);
/// <summary>
/// Update user details (first/last name, phone, password)
/// </summary>
/// <remarks>
///
/// </remarks>
/// <exception cref="BreCalClient.misc.Client.ApiException">Thrown when fails to make API call</exception>
/// <param name="userDetails">User details</param>
/// <param name="operationIndex">Index associated with the operation.</param>
/// <returns>ApiResponse of Object(void)</returns>
ApiResponse<Object> UserPutWithHttpInfo(UserDetails userDetails, int operationIndex = 0);
#endregion Synchronous Operations
}
/// <summary>
@ -572,6 +591,30 @@ namespace BreCalClient.misc.Api
/// <param name="cancellationToken">Cancellation Token to cancel the request.</param>
/// <returns>Task of ApiResponse</returns>
System.Threading.Tasks.Task<ApiResponse<Object>> TimesPutWithHttpInfoAsync(Times times, int operationIndex = 0, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
/// <summary>
/// Update user details (first/last name, phone, password)
/// </summary>
/// <remarks>
///
/// </remarks>
/// <exception cref="BreCalClient.misc.Client.ApiException">Thrown when fails to make API call</exception>
/// <param name="userDetails">User details</param>
/// <param name="operationIndex">Index associated with the operation.</param>
/// <param name="cancellationToken">Cancellation Token to cancel the request.</param>
/// <returns>Task of void</returns>
System.Threading.Tasks.Task UserPutAsync(UserDetails userDetails, int operationIndex = 0, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
/// <summary>
/// Update user details (first/last name, phone, password)
/// </summary>
/// <remarks>
///
/// </remarks>
/// <exception cref="BreCalClient.misc.Client.ApiException">Thrown when fails to make API call</exception>
/// <param name="userDetails">User details</param>
/// <param name="operationIndex">Index associated with the operation.</param>
/// <param name="cancellationToken">Cancellation Token to cancel the request.</param>
/// <returns>Task of ApiResponse</returns>
System.Threading.Tasks.Task<ApiResponse<Object>> UserPutWithHttpInfoAsync(UserDetails userDetails, int operationIndex = 0, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
#endregion Asynchronous Operations
}
/// <summary>
@ -2143,6 +2186,134 @@ namespace BreCalClient.misc.Api
}
return localVarResponse;
}
/// <summary>
/// Update user details (first/last name, phone, password)
/// </summary>
/// <exception cref="BreCalClient.misc.Client.ApiException">Thrown when fails to make API call</exception>
/// <param name="userDetails">User details</param>
/// <param name="operationIndex">Index associated with the operation.</param>
/// <returns></returns>
public void UserPut(UserDetails userDetails, int operationIndex = 0)
{
UserPutWithHttpInfo(userDetails);
}
/// <summary>
/// Update user details (first/last name, phone, password)
/// </summary>
/// <exception cref="BreCalClient.misc.Client.ApiException">Thrown when fails to make API call</exception>
/// <param name="userDetails">User details</param>
/// <param name="operationIndex">Index associated with the operation.</param>
/// <returns>ApiResponse of Object(void)</returns>
public BreCalClient.misc.Client.ApiResponse<Object> UserPutWithHttpInfo(UserDetails userDetails, int operationIndex = 0)
{
// verify the required parameter 'userDetails' is set
if (userDetails == null)
{
throw new BreCalClient.misc.Client.ApiException(400, "Missing required parameter 'userDetails' when calling DefaultApi->UserPut");
}
BreCalClient.misc.Client.RequestOptions localVarRequestOptions = new BreCalClient.misc.Client.RequestOptions();
string[] _contentTypes = new string[] {
"application/json"
};
// to determine the Accept header
string[] _accepts = new string[] {
"application/json"
};
var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes);
if (localVarContentType != null)
{
localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType);
}
var localVarAccept = BreCalClient.misc.Client.ClientUtils.SelectHeaderAccept(_accepts);
if (localVarAccept != null)
{
localVarRequestOptions.HeaderParameters.Add("Accept", localVarAccept);
}
localVarRequestOptions.Data = userDetails;
localVarRequestOptions.Operation = "DefaultApi.UserPut";
localVarRequestOptions.OperationIndex = operationIndex;
// authentication (ApiKey) required
if (!string.IsNullOrEmpty(this.Configuration.GetApiKeyWithPrefix("Authorization")))
{
localVarRequestOptions.HeaderParameters.Add("Authorization", this.Configuration.GetApiKeyWithPrefix("Authorization"));
}
// make the HTTP request
var localVarResponse = this.Client.Put<Object>("/user", localVarRequestOptions, this.Configuration);
if (this.ExceptionFactory != null)
{
Exception _exception = this.ExceptionFactory("UserPut", localVarResponse);
if (_exception != null)
{
throw _exception;
}
}
return localVarResponse;
}
/// <summary>
/// Update user details (first/last name, phone, password)
/// </summary>
/// <exception cref="BreCalClient.misc.Client.ApiException">Thrown when fails to make API call</exception>
/// <param name="userDetails">User details</param>
/// <param name="operationIndex">Index associated with the operation.</param>
/// <param name="cancellationToken">Cancellation Token to cancel the request.</param>
/// <returns>Task of void</returns>
public async System.Threading.Tasks.Task UserPutAsync(UserDetails userDetails, int operationIndex = 0, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
{
await UserPutWithHttpInfoAsync(userDetails, operationIndex, cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Update user details (first/last name, phone, password)
/// </summary>
/// <exception cref="BreCalClient.misc.Client.ApiException">Thrown when fails to make API call</exception>
/// <param name="userDetails">User details</param>
/// <param name="operationIndex">Index associated with the operation.</param>
/// <param name="cancellationToken">Cancellation Token to cancel the request.</param>
/// <returns>Task of ApiResponse</returns>
public async System.Threading.Tasks.Task<BreCalClient.misc.Client.ApiResponse<Object>> UserPutWithHttpInfoAsync(UserDetails userDetails, int operationIndex = 0, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
{
// verify the required parameter 'userDetails' is set
if (userDetails == null)
{
throw new BreCalClient.misc.Client.ApiException(400, "Missing required parameter 'userDetails' when calling DefaultApi->UserPut");
}
BreCalClient.misc.Client.RequestOptions localVarRequestOptions = new BreCalClient.misc.Client.RequestOptions();
string[] _contentTypes = new string[] {
"application/json"
};
// to determine the Accept header
string[] _accepts = new string[] {
"application/json"
};
var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes);
if (localVarContentType != null)
{
localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType);
}
var localVarAccept = BreCalClient.misc.Client.ClientUtils.SelectHeaderAccept(_accepts);
if (localVarAccept != null)
{
localVarRequestOptions.HeaderParameters.Add("Accept", localVarAccept);
}
localVarRequestOptions.Data = userDetails;
localVarRequestOptions.Operation = "DefaultApi.UserPut";
localVarRequestOptions.OperationIndex = operationIndex;
// authentication (ApiKey) required
if (!string.IsNullOrEmpty(this.Configuration.GetApiKeyWithPrefix("Authorization")))
{
localVarRequestOptions.HeaderParameters.Add("Authorization", this.Configuration.GetApiKeyWithPrefix("Authorization"));
}
// make the HTTP request
var localVarResponse = await this.AsynchronousClient.PutAsync<Object>("/user", localVarRequestOptions, this.Configuration, cancellationToken).ConfigureAwait(false);
if (this.ExceptionFactory != null)
{
Exception _exception = this.ExceptionFactory("UserPut", localVarResponse);
if (_exception != null)
{
throw _exception;
}
}
return localVarResponse;
}
}
}
@ -7044,3 +7215,189 @@ namespace BreCalClient.misc.Model
}
}
/*
* Bremen calling API
*
* Administer DEBRE ship calls, times and notifications
*
* The version of the OpenAPI document: 0.3.0
* Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git
*/
namespace BreCalClient.misc.Model
{
/// <summary>
/// fields that a user may change
/// </summary>
[DataContract(Name = "user_details")]
public partial class UserDetails : IEquatable<UserDetails>, IValidatableObject
{
/// <summary>
/// Initializes a new instance of the <see cref="UserDetails" /> class.
/// </summary>
/// <param name="id">id.</param>
/// <param name="oldPassword">oldPassword.</param>
/// <param name="newPassword">newPassword.</param>
/// <param name="firstName">firstName.</param>
/// <param name="lastName">lastName.</param>
/// <param name="userPhone">userPhone.</param>
public UserDetails(int id = default(int), string oldPassword = default(string), string newPassword = default(string), string firstName = default(string), string lastName = default(string), string userPhone = default(string))
{
this.Id = id;
this.OldPassword = oldPassword;
this.NewPassword = newPassword;
this.FirstName = firstName;
this.LastName = lastName;
this.UserPhone = userPhone;
}
/// <summary>
/// Gets or Sets Id
/// </summary>
[DataMember(Name = "id", EmitDefaultValue = true)]
public int Id { get; set; }
/// <summary>
/// Gets or Sets OldPassword
/// </summary>
[DataMember(Name = "old_password", EmitDefaultValue = true)]
public string OldPassword { get; set; }
/// <summary>
/// Gets or Sets NewPassword
/// </summary>
[DataMember(Name = "new_password", EmitDefaultValue = true)]
public string NewPassword { get; set; }
/// <summary>
/// Gets or Sets FirstName
/// </summary>
[DataMember(Name = "first_name", EmitDefaultValue = true)]
public string FirstName { get; set; }
/// <summary>
/// Gets or Sets LastName
/// </summary>
[DataMember(Name = "last_name", EmitDefaultValue = true)]
public string LastName { get; set; }
/// <summary>
/// Gets or Sets UserPhone
/// </summary>
[DataMember(Name = "user_phone", EmitDefaultValue = true)]
public string UserPhone { get; set; }
/// <summary>
/// Returns the string presentation of the object
/// </summary>
/// <returns>String presentation of the object</returns>
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.Append("class UserDetails {\n");
sb.Append(" Id: ").Append(Id).Append("\n");
sb.Append(" OldPassword: ").Append(OldPassword).Append("\n");
sb.Append(" NewPassword: ").Append(NewPassword).Append("\n");
sb.Append(" FirstName: ").Append(FirstName).Append("\n");
sb.Append(" LastName: ").Append(LastName).Append("\n");
sb.Append(" UserPhone: ").Append(UserPhone).Append("\n");
sb.Append("}\n");
return sb.ToString();
}
/// <summary>
/// Returns the JSON string presentation of the object
/// </summary>
/// <returns>JSON string presentation of the object</returns>
public virtual string ToJson()
{
return Newtonsoft.Json.JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented);
}
/// <summary>
/// Returns true if objects are equal
/// </summary>
/// <param name="input">Object to be compared</param>
/// <returns>Boolean</returns>
public override bool Equals(object input)
{
return this.Equals(input as UserDetails);
}
/// <summary>
/// Returns true if UserDetails instances are equal
/// </summary>
/// <param name="input">Instance of UserDetails to be compared</param>
/// <returns>Boolean</returns>
public bool Equals(UserDetails input)
{
if (input == null)
{
return false;
}
return
(
this.Id == input.Id ||
this.Id.Equals(input.Id)
) &&
(
this.OldPassword == input.OldPassword ||
(this.OldPassword != null &&
this.OldPassword.Equals(input.OldPassword))
) &&
(
this.NewPassword == input.NewPassword ||
(this.NewPassword != null &&
this.NewPassword.Equals(input.NewPassword))
) &&
(
this.FirstName == input.FirstName ||
(this.FirstName != null &&
this.FirstName.Equals(input.FirstName))
) &&
(
this.LastName == input.LastName ||
(this.LastName != null &&
this.LastName.Equals(input.LastName))
) &&
(
this.UserPhone == input.UserPhone ||
(this.UserPhone != null &&
this.UserPhone.Equals(input.UserPhone))
);
}
/// <summary>
/// Gets the hash code
/// </summary>
/// <returns>Hash code</returns>
public override int GetHashCode()
{
unchecked // Overflow is fine, just wrap
{
int hashCode = 41;
hashCode = (hashCode * 59) + this.Id.GetHashCode();
if (this.OldPassword != null)
{
hashCode = (hashCode * 59) + this.OldPassword.GetHashCode();
}
if (this.NewPassword != null)
{
hashCode = (hashCode * 59) + this.NewPassword.GetHashCode();
}
if (this.FirstName != null)
{
hashCode = (hashCode * 59) + this.FirstName.GetHashCode();
}
if (this.LastName != null)
{
hashCode = (hashCode * 59) + this.LastName.GetHashCode();
}
if (this.UserPhone != null)
{
hashCode = (hashCode * 59) + this.UserPhone.GetHashCode();
}
return hashCode;
}
}
/// <summary>
/// To validate all properties of the instance
/// </summary>
/// <param name="validationContext">Validation context</param>
/// <returns>Validation Result</returns>
IEnumerable<System.ComponentModel.DataAnnotations.ValidationResult> IValidatableObject.Validate(ValidationContext validationContext)
{
yield break;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:p = "clr-namespace:BreCalClient.Resources"
mc:Ignorable="d"
Title="Help" Height="250" Width="500">
Title="Help" Height="270" Width="500">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />

View File

@ -1,17 +1,11 @@
using System;
using System.Collections.Generic;
// Copyright (c) 2023 schick Informatik
// Description: Show about info and allow user detail editing
//
using System;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace BreCalClient
{
@ -45,7 +39,7 @@ namespace BreCalClient
private void buttonChangePassword_Click(object sender, RoutedEventArgs e)
{
this.ChangePasswordRequested?.Invoke(this.wpBoxOldPassword.Text, this.wpBoxNewPassword.Text);
this.ChangePasswordRequested?.Invoke(this.wpBoxOldPassword.Password, this.wpBoxNewPassword.Password);
}
private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
@ -57,10 +51,11 @@ namespace BreCalClient
private void wpBoxOldPassword_TextChanged(object sender, TextChangedEventArgs e)
{
this.buttonChangePassword.IsEnabled =
(this.wpBoxOldPassword.Text.Length > 0) &&
(this.wpBoxNewPassword.Text.Length > 0) &&
(this.wpBoxNewPasswordRepeat.Text.Length > 0) &&
(this.wpBoxNewPassword.Text.Equals(this.wpBoxNewPasswordRepeat.Text));
(this.wpBoxOldPassword.Password.Length > 0) &&
(this.wpBoxNewPassword.Password.Length > 0) &&
(this.wpBoxNewPasswordRepeat.Password.Length > 0) &&
this.wpBoxNewPassword.Password.Equals(this.wpBoxNewPasswordRepeat.Password) &&
(!this.wpBoxNewPassword.Password.Equals(this.wpBoxOldPassword.Password));
}
#endregion

View File

@ -134,12 +134,35 @@ namespace BreCalClient
}
}
private void buttonInfo_Click(object sender, RoutedEventArgs e)
{
AboutDialog ad = new();
ad.ChangePasswordRequested += (oldPw, newPw) =>
ad.ChangePasswordRequested += async (oldPw, newPw) =>
{
if (_loginResult != null)
{
UserDetails ud = new()
{
Id = _loginResult.Id,
FirstName = _loginResult.FirstName,
LastName = _loginResult.LastName,
UserPhone = _loginResult.UserPhone,
OldPassword = oldPw,
NewPassword = newPw
};
try
{
await _api.UserPutAsync(ud);
}
catch (Exception ex)
{
this.Dispatcher.Invoke(new Action(() =>
{
MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}));
}
}
};
ad.ShowDialog();
}

View File

@ -11,6 +11,7 @@ from .api import notifications
from .api import berths
from .api import ships
from .api import login
from .api import user
sessions = dict()
@ -39,6 +40,7 @@ def create_app(test_config=None):
app.register_blueprint(berths.bp)
app.register_blueprint(ships.bp)
app.register_blueprint(login.bp)
app.register_blueprint(user.bp)
logging.basicConfig(filename='brecal.log', level=logging.DEBUG, format='%(asctime)s | %(name)s | %(levelname)s | %(message)s')

View File

@ -0,0 +1,24 @@
from flask import Blueprint, request
from ..schemas import model
from .. import impl
from ..services.auth_guard import auth_guard
import json
import logging
bp = Blueprint('user', __name__)
@bp.route('/user', methods=['put'])
@auth_guard() # no restriction by role
def PutUser():
try:
content = request.get_json(force=True)
loadedModel = model.UserSchema().load(data=content, many=False, partial=True)
except Exception as ex:
logging.error(ex)
print(ex)
return json.dumps("bad format"), 400
return impl.user.PutUser(loadedModel)

View File

@ -5,3 +5,4 @@ from . import shipcalls
from . import times
from . import ships
from . import login
from . import user

View File

@ -14,7 +14,7 @@ def GetUser(options):
hash = bcrypt.hashpw(options["password"].encode('utf-8'), bcrypt.gensalt( 12 )).decode('utf8')
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)
data = commands.query("SELECT id, participant_id, first_name, last_name, user_name, user_email, user_phone, password_hash, api_key FROM user WHERE user_name = ?username? OR user_email = ?username?",
data = commands.query("SELECT id, participant_id, first_name, last_name, user_name, user_email, user_phone, password_hash, api_key, created, modified FROM user WHERE user_name = ?username? OR user_email = ?username?",
model=model.User, param={"username" : options["username"]})
pooledConnection.close()
# print(data)

View File

@ -0,0 +1,71 @@
import json
import logging
import pydapper
import bcrypt
from ..schemas import model
from .. import local_db
def PutUser(schemaModel):
"""
:param schemaModel: The deserialized dict of the request
"""
# This updates an *existing* entry
try:
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)
# test if object to update is found
sentinel = object()
theuser = commands.query_single_or_default("SELECT * FROM user where id = ?id?", sentinel, param={"id" : schemaModel["id"]}, model=model.User)
if theuser is sentinel:
pooledConnection.close()
return json.dumps("no such record"), 404, {'Content-Type': 'application/json; charset=utf-8'}
# see if we need to update public fields
if "first_name" in schemaModel or "last_name" in schemaModel or "user_phone" in schemaModel:
query = "UPDATE user SET "
isNotFirst = False
for key in schemaModel.keys():
if key == "id":
continue
if key == "old_password":
continue
if key == "new_password":
continue
if isNotFirst:
query += ", "
isNotFirst = True
query += key + " = ?" + key + "? "
query += "WHERE id = ?id?"
affected_rows = commands.execute(query, param=schemaModel)
# update password if available and old pw is (correctly!) given
if "old_password" in schemaModel and schemaModel["old_password"] and "new_password" in schemaModel and schemaModel["new_password"]:
if bcrypt.checkpw(schemaModel["old_password"].encode("utf-8"), bytes(theuser.password_hash, "utf-8")): # old pw matches
password_hash = bcrypt.hashpw(schemaModel["new_password"].encode('utf-8'), bcrypt.gensalt( 12 )).decode('utf8')
query = "UPDATE user SET password_hash = ?password_hash? WHERE id = ?id?"
commands.execute(query, param={"password_hash" : password_hash, "id" : schemaModel["id"]})
else:
result = {}
result["message"] = "old password invalid"
return json.dumps(result), 400, {'Content-Type': 'application/json; charset=utf-8'}
pooledConnection.close()
return json.dumps({"id" : schemaModel["id"]}), 200
except Exception as ex:
logging.error(ex)
print(ex)
result = {}
result["message"] = "call failed"
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'}

View File

@ -146,6 +146,19 @@ class TimesSchema(Schema):
created = fields.DateTime(Required = False)
modified = fields.DateTime(Required = False)
# deserialize PUT object target
class UserSchema(Schema):
def __init__(self):
super().__init__(unknown=None)
pass
id = fields.Int(required=True)
first_name = fields.Str(required=False)
last_name = fields.Str(required=False)
user_phone = fields.Str(required=False)
old_password = fields.Str(required=False)
new_password = fields.Str(required=False)
@dataclass
class Times:
@ -178,6 +191,8 @@ class User:
user_phone: str
password_hash: str
api_key: str
created: datetime
modified: datetime
@dataclass
class Ship(Schema):