add passkey and login management to User Management
This commit is contained in:
@ -143,15 +143,15 @@ else
|
|||||||
|
|
||||||
if (PageState.QueryString.ContainsKey("key"))
|
if (PageState.QueryString.ContainsKey("key"))
|
||||||
{
|
{
|
||||||
user = await UserService.LinkUserAsync(user, PageState.QueryString["token"], PageState.Site.Settings["ExternalLogin:ProviderType"], PageState.QueryString["key"], PageState.Site.Settings["ExternalLogin:ProviderName"]);
|
user = await UserService.AddLoginAsync(user, PageState.QueryString["token"], PageState.Site.Settings["ExternalLogin:ProviderType"], PageState.QueryString["key"], PageState.Site.Settings["ExternalLogin:ProviderName"]);
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
await logger.LogInformation(LogFunction.Security, "External Login Linkage Successful For Username {Username}", _username);
|
await logger.LogInformation(LogFunction.Security, "User Login Linkage Successful For Username {Username}", _username);
|
||||||
AddModuleMessage(Localizer["Success.Account.Linked"], MessageType.Info);
|
AddModuleMessage(Localizer["Success.Account.Linked"], MessageType.Info);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await logger.LogError(LogFunction.Security, "External Login Linkage Failed For Username {Username}", _username);
|
await logger.LogError(LogFunction.Security, "User Login Linkage Failed For Username {Username}", _username);
|
||||||
AddModuleMessage(Localizer["Message.Account.NotLinked"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.Account.NotLinked"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
_username = "";
|
_username = "";
|
||||||
|
|||||||
@ -654,7 +654,7 @@
|
|||||||
{
|
{
|
||||||
if (_allowpasskeys)
|
if (_allowpasskeys)
|
||||||
{
|
{
|
||||||
_passkeys = await UserService.GetPasskeysAsync();
|
_passkeys = await UserService.GetPasskeysAsync(PageState.User.UserId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -709,7 +709,7 @@
|
|||||||
|
|
||||||
private async Task DeletePasskey(UserPasskey passkey)
|
private async Task DeletePasskey(UserPasskey passkey)
|
||||||
{
|
{
|
||||||
await UserService.DeletePasskeyAsync(passkey.CredentialId);
|
await UserService.DeletePasskeyAsync(PageState.User.UserId, passkey.CredentialId);
|
||||||
await GetPasskeys();
|
await GetPasskeys();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
@ -718,7 +718,7 @@
|
|||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(_passkeyName))
|
if (!string.IsNullOrEmpty(_passkeyName))
|
||||||
{
|
{
|
||||||
await UserService.UpdatePasskeyAsync(new UserPasskey { CredentialId = _passkeyId, Name = _passkeyName });
|
await UserService.UpdatePasskeyAsync(new UserPasskey { CredentialId = _passkeyId, Name = _passkeyName, UserId = PageState.User.UserId });
|
||||||
await GetPasskeys();
|
await GetPasskeys();
|
||||||
_passkeyName = "";
|
_passkeyName = "";
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
@ -736,13 +736,13 @@
|
|||||||
{
|
{
|
||||||
if (_allowexternallogin)
|
if (_allowexternallogin)
|
||||||
{
|
{
|
||||||
_logins = await UserService.GetLoginsAsync();
|
_logins = await UserService.GetLoginsAsync(PageState.User.UserId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DeleteLogin(UserLogin login)
|
private async Task DeleteLogin(UserLogin login)
|
||||||
{
|
{
|
||||||
await UserService.DeleteLoginAsync(login.Provider, login.Key);
|
await UserService.DeleteLoginAsync(PageState.User.UserId, login.Provider, login.Key);
|
||||||
await GetLogins();
|
await GetLogins();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -103,6 +103,53 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<br /><br />
|
||||||
|
@if (_allowpasskeys)
|
||||||
|
{
|
||||||
|
<Section Name="Passkeys" Heading="Passkeys" ResourceKey="Passkeys">
|
||||||
|
@if (_passkeys != null && _passkeys.Count > 0)
|
||||||
|
{
|
||||||
|
<Pager Items="@_passkeys">
|
||||||
|
<Header>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
|
<th>@Localizer["Passkey"]</th>
|
||||||
|
</Header>
|
||||||
|
<Row>
|
||||||
|
<td><ActionDialog Action="Delete" OnClick="@(async () => await DeletePasskey(context))" ResourceKey="DeletePasskey" Class="btn btn-danger" Header="Delete Passkey" Message="@string.Format(Localizer["Confirm.Passkey.Delete", context.Name])" /></td>
|
||||||
|
<td>@context.Name</td>
|
||||||
|
</Row>
|
||||||
|
</Pager>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div>@Localizer["Message.Passkeys.None"]</div>
|
||||||
|
}
|
||||||
|
</Section>
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
@if (_allowexternallogin)
|
||||||
|
{
|
||||||
|
<Section Name="Logins" Heading="Logins" ResourceKey="Logins">
|
||||||
|
@if (_logins != null && _logins.Count > 0)
|
||||||
|
{
|
||||||
|
<Pager Items="@_logins">
|
||||||
|
<Header>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
|
<th>@Localizer["Login"]</th>
|
||||||
|
</Header>
|
||||||
|
<Row>
|
||||||
|
<td><ActionDialog Action="Delete" OnClick="@(async () => await DeleteLogin(context))" ResourceKey="DeleteLogin" Class="btn btn-danger" Header="Delete Login" Message="@string.Format(Localizer["Confirm.Login.Delete", context.Name])" /></td>
|
||||||
|
<td>@context.Name</td>
|
||||||
|
</Row>
|
||||||
|
</Pager>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div>@Localizer["Message.Logins.None"]</div>
|
||||||
|
}
|
||||||
|
</Section>
|
||||||
|
<br />
|
||||||
|
}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Profile" Heading="Profile" ResourceKey="Profile">
|
<TabPanel Name="Profile" Heading="Profile" ResourceKey="Profile">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@ -173,24 +220,30 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private List<Models.TimeZone> _timezones;
|
|
||||||
private bool _initialized = false;
|
private bool _initialized = false;
|
||||||
private string _passwordrequirements;
|
private bool _allowpasskeys = false;
|
||||||
|
private bool _allowexternallogin = false;
|
||||||
|
|
||||||
private int _userid;
|
private int _userid;
|
||||||
private string _username = string.Empty;
|
private string _username = string.Empty;
|
||||||
private string _password = string.Empty;
|
|
||||||
private string _passwordtype = "password";
|
|
||||||
private string _togglepassword = string.Empty;
|
|
||||||
private string _confirm = string.Empty;
|
|
||||||
private string _email = string.Empty;
|
private string _email = string.Empty;
|
||||||
private string _confirmed = string.Empty;
|
private string _confirmed = string.Empty;
|
||||||
private string _displayname = string.Empty;
|
private string _displayname = string.Empty;
|
||||||
|
private List<Models.TimeZone> _timezones;
|
||||||
private string _timezoneid = string.Empty;
|
private string _timezoneid = string.Empty;
|
||||||
private string _isdeleted;
|
private string _isdeleted;
|
||||||
private string _lastlogin;
|
private string _lastlogin;
|
||||||
private string _lastipaddress;
|
private string _lastipaddress;
|
||||||
private bool _ishost = false;
|
private bool _ishost = false;
|
||||||
|
|
||||||
|
private string _passwordrequirements;
|
||||||
|
private string _password = string.Empty;
|
||||||
|
private string _passwordtype = "password";
|
||||||
|
private string _togglepassword = string.Empty;
|
||||||
|
private string _confirm = string.Empty;
|
||||||
|
private List<UserPasskey> _passkeys;
|
||||||
|
private List<UserLogin> _logins;
|
||||||
|
|
||||||
private List<Profile> _profiles;
|
private List<Profile> _profiles;
|
||||||
private Dictionary<string, string> _settings;
|
private Dictionary<string, string> _settings;
|
||||||
private string _category = string.Empty;
|
private string _category = string.Empty;
|
||||||
@ -208,19 +261,8 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
_allowpasskeys = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:Passkeys", "false") == "true");
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
_allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
|
||||||
_profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
|
|
||||||
foreach (var profile in _profiles)
|
|
||||||
{
|
|
||||||
if (profile.Options.ToLower().StartsWith("entityname:"))
|
|
||||||
{
|
|
||||||
var options = await SettingService.GetSettingsAsync(profile.Options.Substring(11), -1);
|
|
||||||
options.Add("", $"<{SharedLocalizer["Not Specified"]}>");
|
|
||||||
profile.Options = string.Join(",", options.OrderBy(item => item.Value).Select(kvp => $"{kvp.Key}:{kvp.Value}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_timezones = TimeZoneService.GetTimeZones();
|
|
||||||
|
|
||||||
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int UserId))
|
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int UserId))
|
||||||
{
|
{
|
||||||
@ -232,13 +274,30 @@
|
|||||||
_email = user.Email;
|
_email = user.Email;
|
||||||
_confirmed = user.EmailConfirmed.ToString();
|
_confirmed = user.EmailConfirmed.ToString();
|
||||||
_displayname = user.DisplayName;
|
_displayname = user.DisplayName;
|
||||||
|
_timezones = TimeZoneService.GetTimeZones();
|
||||||
_timezoneid = PageState.User.TimeZoneId;
|
_timezoneid = PageState.User.TimeZoneId;
|
||||||
_isdeleted = user.IsDeleted.ToString();
|
_isdeleted = user.IsDeleted.ToString();
|
||||||
_lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", UtcToLocal(user.LastLoginOn));
|
_lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", UtcToLocal(user.LastLoginOn));
|
||||||
_lastipaddress = user.LastIPAddress;
|
_lastipaddress = user.LastIPAddress;
|
||||||
_ishost = UserSecurity.ContainsRole(user.Roles, RoleNames.Host);
|
_ishost = UserSecurity.ContainsRole(user.Roles, RoleNames.Host);
|
||||||
|
|
||||||
_settings = user.Settings;
|
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||||
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
|
await GetPasskeys();
|
||||||
|
await GetLogins();
|
||||||
|
|
||||||
|
_profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
|
||||||
|
foreach (var profile in _profiles)
|
||||||
|
{
|
||||||
|
if (profile.Options.ToLower().StartsWith("entityname:"))
|
||||||
|
{
|
||||||
|
var options = await SettingService.GetSettingsAsync(profile.Options.Substring(11), -1);
|
||||||
|
options.Add("", $"<{SharedLocalizer["Not Specified"]}>");
|
||||||
|
profile.Options = string.Join(",", options.OrderBy(item => item.Value).Select(kvp => $"{kvp.Key}:{kvp.Value}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_settings = user.Settings;
|
||||||
|
|
||||||
_createdby = user.CreatedBy;
|
_createdby = user.CreatedBy;
|
||||||
_createdon = user.CreatedOn;
|
_createdon = user.CreatedOn;
|
||||||
_modifiedby = user.ModifiedBy;
|
_modifiedby = user.ModifiedBy;
|
||||||
@ -358,6 +417,35 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task GetPasskeys()
|
||||||
|
{
|
||||||
|
if (_allowpasskeys)
|
||||||
|
{
|
||||||
|
_passkeys = await UserService.GetPasskeysAsync(_userid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private async Task DeletePasskey(UserPasskey passkey)
|
||||||
|
{
|
||||||
|
await UserService.DeletePasskeyAsync(_userid, passkey.CredentialId);
|
||||||
|
await GetPasskeys();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GetLogins()
|
||||||
|
{
|
||||||
|
if (_allowexternallogin)
|
||||||
|
{
|
||||||
|
_logins = await UserService.GetLoginsAsync(_userid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DeleteLogin(UserLogin login)
|
||||||
|
{
|
||||||
|
await UserService.DeleteLoginAsync(_userid, login.Provider, login.Key);
|
||||||
|
await GetLogins();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
private bool ValidateProfiles()
|
private bool ValidateProfiles()
|
||||||
{
|
{
|
||||||
foreach (Profile profile in _profiles)
|
foreach (Profile profile in _profiles)
|
||||||
|
|||||||
@ -222,4 +222,34 @@
|
|||||||
<data name="Security.Heading" xml:space="preserve">
|
<data name="Security.Heading" xml:space="preserve">
|
||||||
<value>Security</value>
|
<value>Security</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Passkeys.Heading" xml:space="preserve">
|
||||||
|
<value>Passkeys</value>
|
||||||
|
</data>
|
||||||
|
<data name="Logins.Heading" xml:space="preserve">
|
||||||
|
<value>Logins</value>
|
||||||
|
</data>
|
||||||
|
<data name="Passkey" xml:space="preserve">
|
||||||
|
<value>Passkey</value>
|
||||||
|
</data>
|
||||||
|
<data name="Login" xml:space="preserve">
|
||||||
|
<value>Login</value>
|
||||||
|
</data>
|
||||||
|
<data name="DeletePasskey.Header" xml:space="preserve">
|
||||||
|
<value>Delete Passkey</value>
|
||||||
|
</data>
|
||||||
|
<data name="DeleteLogin.Header" xml:space="preserve">
|
||||||
|
<value>Delete Login</value>
|
||||||
|
</data>
|
||||||
|
<data name="Confirm.Passkey.Delete" xml:space="preserve">
|
||||||
|
<value>Are You Sure You Wish To Delete {0}?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Confirm.Login.Delete" xml:space="preserve">
|
||||||
|
<value>Are You Sure You Wish To Delete {0}?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Message.Passkeys.None" xml:space="preserve">
|
||||||
|
<value>You Have Not Created Any Passkeys</value>
|
||||||
|
</data>
|
||||||
|
<data name="Message.Logins.None" xml:space="preserve">
|
||||||
|
<value>You Do Not Have Any External Logins For This Site</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@ -147,17 +147,6 @@ namespace Oqtane.Services
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<string> GetPersonalAccessTokenAsync();
|
Task<string> GetPersonalAccessTokenAsync();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Link an external login with a local user account
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="user">The <see cref="User"/> we're verifying</param>
|
|
||||||
/// <param name="token">A Hash value in the URL which verifies this user got the e-mail (containing this token)</param>
|
|
||||||
/// <param name="type">External Login provider type</param>
|
|
||||||
/// <param name="key">External Login provider key</param>
|
|
||||||
/// <param name="name">External Login provider display name</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
Task<User> LinkUserAsync(User user, string token, string type, string key, string name);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get password requirements for site
|
/// Get password requirements for site
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -177,8 +166,9 @@ namespace Oqtane.Services
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get passkeys for a user
|
/// Get passkeys for a user
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="userId"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<List<UserPasskey>> GetPasskeysAsync();
|
Task<List<UserPasskey>> GetPasskeysAsync(int userId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Update a user passkey
|
/// Update a user passkey
|
||||||
@ -190,23 +180,37 @@ namespace Oqtane.Services
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delete a user passkey
|
/// Delete a user passkey
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="userId"></param>
|
||||||
/// <param name="credentialId"></param>
|
/// <param name="credentialId"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task DeletePasskeyAsync(byte[] credentialId);
|
Task DeletePasskeyAsync(int userId, byte[] credentialId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get logins for a user
|
/// Get logins for a user
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="userId"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<List<UserLogin>> GetLoginsAsync();
|
Task<List<UserLogin>> GetLoginsAsync(int userId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Link an external login with a local user account
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">The <see cref="User"/> we're verifying</param>
|
||||||
|
/// <param name="token">A Hash value in the URL which verifies this user got the e-mail (containing this token)</param>
|
||||||
|
/// <param name="type">External Login provider type</param>
|
||||||
|
/// <param name="key">External Login provider key</param>
|
||||||
|
/// <param name="name">External Login provider display name</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<User> AddLoginAsync(User user, string token, string type, string key, string name);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delete a user login
|
/// Delete a user login
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="userId"></param>
|
||||||
/// <param name="provider"></param>
|
/// <param name="provider"></param>
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task DeleteLoginAsync(string provider, string key);
|
Task DeleteLoginAsync(int userId, string provider, string key);
|
||||||
}
|
}
|
||||||
|
|
||||||
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
|
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
|
||||||
@ -253,7 +257,7 @@ namespace Oqtane.Services
|
|||||||
|
|
||||||
public async Task<User> LoginUserAsync(User user, bool setCookie, bool isPersistent)
|
public async Task<User> LoginUserAsync(User user, bool setCookie, bool isPersistent)
|
||||||
{
|
{
|
||||||
return await PostJsonAsync<User>($"{Apiurl}/login?setcookie={setCookie}&persistent={isPersistent}", user);
|
return await PostJsonAsync<User>($"{Apiurl}/signin?setcookie={setCookie}&persistent={isPersistent}", user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task LogoutUserAsync(User user)
|
public async Task LogoutUserAsync(User user)
|
||||||
@ -306,11 +310,6 @@ namespace Oqtane.Services
|
|||||||
return await GetStringAsync($"{Apiurl}/personalaccesstoken");
|
return await GetStringAsync($"{Apiurl}/personalaccesstoken");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<User> LinkUserAsync(User user, string token, string type, string key, string name)
|
|
||||||
{
|
|
||||||
return await PostJsonAsync<User>($"{Apiurl}/link?token={token}&type={type}&key={key}&name={name}", user);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> GetPasswordRequirementsAsync(int siteId)
|
public async Task<string> GetPasswordRequirementsAsync(int siteId)
|
||||||
{
|
{
|
||||||
var requirements = await GetJsonAsync<Dictionary<string, string>>($"{Apiurl}/passwordrequirements/{siteId}");
|
var requirements = await GetJsonAsync<Dictionary<string, string>>($"{Apiurl}/passwordrequirements/{siteId}");
|
||||||
@ -338,9 +337,9 @@ namespace Oqtane.Services
|
|||||||
return await PostJsonAsync<Dictionary<string, string>>($"{Apiurl}/import?siteid={siteId}&fileid={fileId}¬ify={notify}", null);
|
return await PostJsonAsync<Dictionary<string, string>>($"{Apiurl}/import?siteid={siteId}&fileid={fileId}¬ify={notify}", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<UserPasskey>> GetPasskeysAsync()
|
public async Task<List<UserPasskey>> GetPasskeysAsync(int userId)
|
||||||
{
|
{
|
||||||
return await GetJsonAsync<List<UserPasskey>>($"{Apiurl}/passkey");
|
return await GetJsonAsync<List<UserPasskey>>($"{Apiurl}/passkey?id={userId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<UserPasskey> UpdatePasskeyAsync(UserPasskey passkey)
|
public async Task<UserPasskey> UpdatePasskeyAsync(UserPasskey passkey)
|
||||||
@ -348,19 +347,24 @@ namespace Oqtane.Services
|
|||||||
return await PutJsonAsync<UserPasskey>($"{Apiurl}/passkey", passkey);
|
return await PutJsonAsync<UserPasskey>($"{Apiurl}/passkey", passkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeletePasskeyAsync(byte[] credentialId)
|
public async Task DeletePasskeyAsync(int userId, byte[] credentialId)
|
||||||
{
|
{
|
||||||
await DeleteAsync($"{Apiurl}/passkey?id={Base64Url.EncodeToString(credentialId)}");
|
await DeleteAsync($"{Apiurl}/passkey?id={userId}&credential={Base64Url.EncodeToString(credentialId)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<UserLogin>> GetLoginsAsync()
|
public async Task<List<UserLogin>> GetLoginsAsync(int userId)
|
||||||
{
|
{
|
||||||
return await GetJsonAsync<List<UserLogin>>($"{Apiurl}/login");
|
return await GetJsonAsync<List<UserLogin>>($"{Apiurl}/login?id={userId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteLoginAsync(string provider, string key)
|
public async Task<User> AddLoginAsync(User user, string token, string type, string key, string name)
|
||||||
{
|
{
|
||||||
await DeleteAsync($"{Apiurl}/login?provider={provider}&key={key}");
|
return await PostJsonAsync<User>($"{Apiurl}/login?token={token}&type={type}&key={key}&name={name}", user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteLoginAsync(int userId, string provider, string key)
|
||||||
|
{
|
||||||
|
await DeleteAsync($"{Apiurl}/login?id={userId}&provider={provider}&key={key}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
using System.Security.Policy;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
@ -240,8 +241,8 @@ namespace Oqtane.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST api/<controller>/login
|
// POST api/<controller>/signin
|
||||||
[HttpPost("login")]
|
[HttpPost("signin")]
|
||||||
public async Task<User> Login([FromBody] User user, bool setCookie, bool isPersistent)
|
public async Task<User> Login([FromBody] User user, bool setCookie, bool isPersistent)
|
||||||
{
|
{
|
||||||
if (ModelState.IsValid)
|
if (ModelState.IsValid)
|
||||||
@ -330,22 +331,6 @@ namespace Oqtane.Controllers
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST api/<controller>/link
|
|
||||||
[HttpPost("link")]
|
|
||||||
public async Task<User> Link([FromBody] User user, string token, string type, string key, string name)
|
|
||||||
{
|
|
||||||
if (ModelState.IsValid)
|
|
||||||
{
|
|
||||||
user = await _userManager.LinkExternalAccount(user, token, type, key, name);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "External Login Linkage Failed For {Username} And Token {Token}", user.Username, token);
|
|
||||||
user = null;
|
|
||||||
}
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET api/<controller>/validate/x
|
// GET api/<controller>/validate/x
|
||||||
[HttpGet("validateuser")]
|
[HttpGet("validateuser")]
|
||||||
public async Task<UserValidateResult> ValidateUser(string username, string email, string password)
|
public async Task<UserValidateResult> ValidateUser(string username, string email, string password)
|
||||||
@ -466,12 +451,21 @@ namespace Oqtane.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: api/<controller>/passkey
|
// GET: api/<controller>/passkey?id=x
|
||||||
[HttpGet("passkey")]
|
[HttpGet("passkey")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<IEnumerable<UserPasskey>> GetPasskeys()
|
public async Task<IEnumerable<UserPasskey>> GetPasskeys(int id)
|
||||||
{
|
{
|
||||||
return await _userManager.GetPasskeys(_userPermissions.GetUser(User).UserId, _tenantManager.GetAlias().SiteId);
|
if (_userPermissions.IsAuthorized(User, _tenantManager.GetAlias().SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || _userPermissions.GetUser(User).UserId == id)
|
||||||
|
{
|
||||||
|
return await _userManager.GetPasskeys(id, _tenantManager.GetAlias().SiteId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Passkey Get Attempt {UserId} {SiteId}", id, _tenantManager.GetAlias().SiteId);
|
||||||
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PUT api/<controller>/passkey
|
// PUT api/<controller>/passkey
|
||||||
@ -481,10 +475,17 @@ namespace Oqtane.Controllers
|
|||||||
{
|
{
|
||||||
if (ModelState.IsValid)
|
if (ModelState.IsValid)
|
||||||
{
|
{
|
||||||
// passkey name is prefixed with SiteId for multi-tenancy
|
if (_userPermissions.IsAuthorized(User, _tenantManager.GetAlias().SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || _userPermissions.GetUser(User).UserId == passkey.UserId)
|
||||||
passkey.Name = $"{_tenantManager.GetAlias().SiteId}:" + passkey.Name;
|
{
|
||||||
passkey.UserId = _userPermissions.GetUser(User).UserId;
|
// passkey name is prefixed with SiteId for multi-tenancy
|
||||||
await _userManager.UpdatePasskey(passkey);
|
passkey.Name = $"{_tenantManager.GetAlias().SiteId}:" + passkey.Name;
|
||||||
|
await _userManager.UpdatePasskey(passkey);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Passkey Put Attempt {PassKey}", passkey);
|
||||||
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -493,28 +494,70 @@ namespace Oqtane.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DELETE api/<controller>/passkey?id=x
|
// DELETE api/<controller>/passkey?id=x&credential=y
|
||||||
[HttpDelete("passkey")]
|
[HttpDelete("passkey")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task DeletePasskey(string id)
|
public async Task DeletePasskey(int id, string credential)
|
||||||
{
|
{
|
||||||
await _userManager.DeletePasskey(_userPermissions.GetUser(User).UserId, Base64Url.DecodeFromChars(id));
|
if (_userPermissions.IsAuthorized(User, _tenantManager.GetAlias().SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || _userPermissions.GetUser(User).UserId == id)
|
||||||
|
{
|
||||||
|
await _userManager.DeletePasskey(id, Base64Url.DecodeFromChars(credential));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Passkey Delete Attempt {UserId} {Credential}", id, credential);
|
||||||
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: api/<controller>/login
|
// GET: api/<controller>/login?id=x
|
||||||
[HttpGet("login")]
|
[HttpGet("login")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<IEnumerable<UserLogin>> GetLogins()
|
public async Task<IEnumerable<UserLogin>> GetLogins(int id)
|
||||||
{
|
{
|
||||||
return await _userManager.GetLogins(_userPermissions.GetUser(User).UserId, _tenantManager.GetAlias().SiteId);
|
if (_userPermissions.IsAuthorized(User, _tenantManager.GetAlias().SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || _userPermissions.GetUser(User).UserId == id)
|
||||||
|
{
|
||||||
|
return await _userManager.GetLogins(id, _tenantManager.GetAlias().SiteId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Login Get Attempt {UserId} {SiteId}", id, _tenantManager.GetAlias().SiteId);
|
||||||
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DELETE api/<controller>/login?provider=x&key=y
|
// PUT api/<controller>/login
|
||||||
|
[HttpPost("login")]
|
||||||
|
public async Task<User> AddLogin([FromBody] User user, string token, string type, string key, string name)
|
||||||
|
{
|
||||||
|
if (ModelState.IsValid)
|
||||||
|
{
|
||||||
|
user = await _userManager.AddLogin(user, token, type, key, name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Login Post Attempt {Username} {Token}", user.Username, token);
|
||||||
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
|
user = null;
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE api/<controller>/login?id=x&provider=y&key=z
|
||||||
[HttpDelete("login")]
|
[HttpDelete("login")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task DeleteLogin(string provider, string key)
|
public async Task DeleteLogin(int id, string provider, string key)
|
||||||
{
|
{
|
||||||
await _userManager.DeleteLogin(_userPermissions.GetUser(User).UserId, provider, key);
|
if (_userPermissions.IsAuthorized(User, _tenantManager.GetAlias().SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || _userPermissions.GetUser(User).UserId == id)
|
||||||
|
{
|
||||||
|
await _userManager.DeleteLogin(id, provider, key);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Login Delete Attempt {UserId} {Provider} {Key}", id, provider, key);
|
||||||
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,7 +33,6 @@ namespace Oqtane.Managers
|
|||||||
Task ForgotPassword(User user);
|
Task ForgotPassword(User user);
|
||||||
Task<User> ResetPassword(User user, string token);
|
Task<User> ResetPassword(User user, string token);
|
||||||
User VerifyTwoFactor(User user, string token);
|
User VerifyTwoFactor(User user, string token);
|
||||||
Task<User> LinkExternalAccount(User user, string token, string type, string key, string name);
|
|
||||||
Task<UserValidateResult> ValidateUser(string username, string email, string password);
|
Task<UserValidateResult> ValidateUser(string username, string email, string password);
|
||||||
Task<bool> ValidatePassword(string password);
|
Task<bool> ValidatePassword(string password);
|
||||||
Task<Dictionary<string, string>> ImportUsers(int siteId, string filePath, bool notify);
|
Task<Dictionary<string, string>> ImportUsers(int siteId, string filePath, bool notify);
|
||||||
@ -41,6 +40,7 @@ namespace Oqtane.Managers
|
|||||||
Task UpdatePasskey(UserPasskey passkey);
|
Task UpdatePasskey(UserPasskey passkey);
|
||||||
Task DeletePasskey(int userId, byte[] credentialId);
|
Task DeletePasskey(int userId, byte[] credentialId);
|
||||||
Task<List<UserLogin>> GetLogins(int userId, int siteId);
|
Task<List<UserLogin>> GetLogins(int userId, int siteId);
|
||||||
|
Task<User> AddLogin(User user, string token, string type, string key, string name);
|
||||||
Task DeleteLogin(int userId, string provider, string key);
|
Task DeleteLogin(int userId, string provider, string key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -588,29 +588,6 @@ namespace Oqtane.Managers
|
|||||||
}
|
}
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<User> LinkExternalAccount(User user, string token, string type, string key, string name)
|
|
||||||
{
|
|
||||||
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
|
|
||||||
if (identityuser != null && !string.IsNullOrEmpty(token))
|
|
||||||
{
|
|
||||||
var result = await _identityUserManager.ConfirmEmailAsync(identityuser, token);
|
|
||||||
if (result.Succeeded)
|
|
||||||
{
|
|
||||||
// make LoginProvider multi-tenant aware
|
|
||||||
type += ":" + user.SiteId.ToString();
|
|
||||||
await _identityUserManager.AddLoginAsync(identityuser, new UserLoginInfo(type, key, name));
|
|
||||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "External Login Linkage Successful For {Username} And Provider {Provider}", user.Username, type);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "External Login Linkage Failed For {Username} - Error {Error}", user.Username, string.Join(" ", result.Errors.ToList().Select(e => e.Description)));
|
|
||||||
user = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<UserValidateResult> ValidateUser(string username, string email, string password)
|
public async Task<UserValidateResult> ValidateUser(string username, string email, string password)
|
||||||
{
|
{
|
||||||
var validateResult = new UserValidateResult { Succeeded = true };
|
var validateResult = new UserValidateResult { Succeeded = true };
|
||||||
@ -902,6 +879,29 @@ namespace Oqtane.Managers
|
|||||||
return logins;
|
return logins;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<User> AddLogin(User user, string token, string type, string key, string name)
|
||||||
|
{
|
||||||
|
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
|
||||||
|
if (identityuser != null && !string.IsNullOrEmpty(token))
|
||||||
|
{
|
||||||
|
var result = await _identityUserManager.ConfirmEmailAsync(identityuser, token);
|
||||||
|
if (result.Succeeded)
|
||||||
|
{
|
||||||
|
// make LoginProvider multi-tenant aware
|
||||||
|
type += ":" + user.SiteId.ToString();
|
||||||
|
await _identityUserManager.AddLoginAsync(identityuser, new UserLoginInfo(type, key, name));
|
||||||
|
_logger.Log(LogLevel.Information, this, LogFunction.Security, "External Login Linkage Successful For {Username} And Provider {Provider}", user.Username, type);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "External Login Linkage Failed For {Username} - Error {Error}", user.Username, string.Join(" ", result.Errors.ToList().Select(e => e.Description)));
|
||||||
|
user = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task DeleteLogin(int userId, string provider, string key)
|
public async Task DeleteLogin(int userId, string provider, string key)
|
||||||
{
|
{
|
||||||
var user = _users.GetUser(userId);
|
var user = _users.GetUser(userId);
|
||||||
|
|||||||
Reference in New Issue
Block a user