aboutsummaryrefslogtreecommitdiff
path: root/VNLib.Plugins.Essentials.Accounts/Endpoints/PasswordResetEndpoint.cs
blob: 81bba514f1e3dfa7760ad8938c803034b521b93e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
using System;
using System.Net;
using System.Text.Json;
using System.Threading.Tasks;
using System.Collections.Generic;

using FluentValidation;

using VNLib.Utils.Memory;
using VNLib.Utils.Extensions;
using VNLib.Plugins.Essentials.Users;
using VNLib.Plugins.Essentials.Extensions;
using VNLib.Plugins.Extensions.Validation;
using VNLib.Plugins.Extensions.Loading;
using VNLib.Plugins.Extensions.Loading.Users;
using VNLib.Plugins.Essentials.Endpoints;

namespace VNLib.Plugins.Essentials.Accounts.Endpoints
{

    /// <summary>
    /// Password reset for user's that are logged in and know 
    /// their passwords to reset their MFA methods
    /// </summary>
    [ConfigurationName("password_endpoint")]
    internal sealed class PasswordChangeEndpoint : ProtectedWebEndpoint
    {
        private readonly IUserManager Users;
        private readonly PasswordHashing Passwords;

        public PasswordChangeEndpoint(PluginBase pbase, IReadOnlyDictionary<string, JsonElement> config)
        {
            string? path = config["path"].GetString();
            InitPathAndLog(path, pbase.Log);

            Users = pbase.GetUserManager();
            Passwords = pbase.GetPasswords();
        }

        protected override async ValueTask<VfReturnType> PostAsync(HttpEntity entity)
        {
            ValErrWebMessage webm = new();
            //get the request body
            using JsonDocument? request = await entity.GetJsonFromFileAsync();
            if (request == null)
            {
                webm.Result = "No request specified";
                entity.CloseResponseJson(HttpStatusCode.BadRequest, webm);
                return VfReturnType.VirtualSkip;
            }
            //get the user's old password
            using PrivateString? currentPass = (PrivateString?)request.RootElement.GetPropString("current");
            //Get password as a private string
            using PrivateString? newPass = (PrivateString?)request.RootElement.GetPropString("new_password");
            if (PrivateString.IsNullOrEmpty(currentPass))
            {
                webm.Result = "You must specifiy your current password.";
                entity.CloseResponseJson(HttpStatusCode.UnprocessableEntity, webm);
                return VfReturnType.VirtualSkip;
            }
            if (PrivateString.IsNullOrEmpty(newPass))
            {
                webm.Result = "You must specifiy a new password.";
                entity.CloseResponseJson(HttpStatusCode.UnprocessableEntity, webm);
                return VfReturnType.VirtualSkip;
            }
            //Test the password against minimum
            if (!AccountValidations.PasswordValidator.Validate((string)newPass, webm))
            {
                entity.CloseResponse(webm);
                return VfReturnType.VirtualSkip;
            }
            if (webm.Assert(!currentPass.Equals(newPass), "Passwords cannot be the same."))
            {
                entity.CloseResponse(webm);
                return VfReturnType.VirtualSkip;
            }
            //get the user's entry in the table
            using IUser?  user = await Users.GetUserAndPassFromIDAsync(entity.Session.UserID);
            if(webm.Assert(user != null, "An error has occured, please log-out and try again"))
            {
                entity.CloseResponse(webm);
                return VfReturnType.VirtualSkip;
            }
            //Make sure the account's origin is a local profile
            if (webm.Assert(user.IsLocalAccount(), "External accounts cannot be modified"))
            {
                entity.CloseResponse(webm);
                return VfReturnType.VirtualSkip;
            }
            //Verify the user's old password
            if (!Passwords.Verify(user.PassHash, currentPass))
            {
                webm.Result = "Please check your current password";
                entity.CloseResponse(webm);
                return VfReturnType.VirtualSkip;
            }
            //Hash the user's new password
            using PrivateString newPassHash = Passwords.Hash(newPass);
            //Update the user's password
            if (!await Users.UpdatePassAsync(user, newPassHash))
            {
                //error
                webm.Result = "Your password could not be updated";
                entity.CloseResponse(webm);
                return VfReturnType.VirtualSkip;
            }
            await user.ReleaseAsync();
            //delete the user's MFA entry so they can re-enable it
            webm.Result = "Your password has been updated";
            webm.Success = true;
            entity.CloseResponse(webm);
            return VfReturnType.VirtualSkip;
        }
    }
}