mirror of https://github.com/Squidex/squidex.git
23 changed files with 535 additions and 73 deletions
@ -0,0 +1,21 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ErrorController.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
|
||||
|
namespace Squidex.Controllers.UI.Error |
||||
|
{ |
||||
|
public class ErrorController : Controller |
||||
|
{ |
||||
|
[Route("error")] |
||||
|
public IActionResult Error() |
||||
|
{ |
||||
|
return View(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ChangePasswordModel.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
|
||||
|
namespace Squidex.Controllers.UI.Profile |
||||
|
{ |
||||
|
public class ChangePasswordModel |
||||
|
{ |
||||
|
[Required(ErrorMessage = "Old Password is required.")] |
||||
|
public string OldPassword { get; set; } |
||||
|
|
||||
|
[Required(ErrorMessage = "Password is required.")] |
||||
|
public string Password { get; set; } |
||||
|
|
||||
|
[Compare(nameof(Password), ErrorMessage = "Passwords must be identitical.")] |
||||
|
public string PasswordConfirm { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ChangeProfileModel.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
|
||||
|
namespace Squidex.Controllers.UI.Profile |
||||
|
{ |
||||
|
public class ChangeProfileModel |
||||
|
{ |
||||
|
[Required(ErrorMessage = "Email is required.")] |
||||
|
[EmailAddress(ErrorMessage = "Email is not valid.")] |
||||
|
public string Email { get; set; } |
||||
|
|
||||
|
[Required(ErrorMessage = "DisplayName is required.")] |
||||
|
public string DisplayName { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,123 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ProfileController.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Authorization; |
||||
|
using Microsoft.AspNetCore.Identity; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using NSwag.Annotations; |
||||
|
using Squidex.Config.Identity; |
||||
|
using Squidex.Infrastructure.Reflection; |
||||
|
using Squidex.Read.Users; |
||||
|
|
||||
|
namespace Squidex.Controllers.UI.Profile |
||||
|
{ |
||||
|
[Authorize] |
||||
|
[SwaggerIgnore] |
||||
|
public class ProfileController : Controller |
||||
|
{ |
||||
|
private readonly UserManager<IUser> userManager; |
||||
|
private readonly IOptions<MyIdentityOptions> identityOptions; |
||||
|
|
||||
|
public ProfileController(UserManager<IUser> userManager, IOptions<MyIdentityOptions> identityOptions) |
||||
|
{ |
||||
|
this.userManager = userManager; |
||||
|
this.identityOptions = identityOptions; |
||||
|
} |
||||
|
|
||||
|
[HttpGet] |
||||
|
[Route("/account/profile")] |
||||
|
public async Task<IActionResult> Profile(string successMessage = null) |
||||
|
{ |
||||
|
var user = await userManager.GetUserAsync(User); |
||||
|
|
||||
|
ViewBag.SuccessMessage = successMessage; |
||||
|
|
||||
|
return View(await GetProfileVM(user)); |
||||
|
} |
||||
|
|
||||
|
[HttpPost] |
||||
|
[Route("/account/profile")] |
||||
|
public Task<IActionResult> Profile(ChangeProfileModel model) |
||||
|
{ |
||||
|
return MakeChangeAsync(async user => |
||||
|
{ |
||||
|
user.UpdateEmail(model.Email); |
||||
|
user.SetDisplayName(model.DisplayName); |
||||
|
|
||||
|
return await userManager.UpdateAsync(user); |
||||
|
}, "Account updated successfully. Please logout and login again to see the changes."); |
||||
|
} |
||||
|
|
||||
|
[HttpPost] |
||||
|
[Route("/account/setpassword")] |
||||
|
public Task<IActionResult> SetPassword(SetPasswordModel model) |
||||
|
{ |
||||
|
return MakeChangeAsync(user => userManager.AddPasswordAsync(user, model.Password), |
||||
|
"Password set successfully."); |
||||
|
} |
||||
|
|
||||
|
[HttpPost] |
||||
|
[Route("/account/changepassword")] |
||||
|
public Task<IActionResult> ChangePassword(ChangePasswordModel model) |
||||
|
{ |
||||
|
return MakeChangeAsync(user => userManager.ChangePasswordAsync(user, model.OldPassword, model.Password), |
||||
|
"Password changed successfully."); |
||||
|
} |
||||
|
|
||||
|
private async Task<IActionResult> MakeChangeAsync(Func<IUser, Task<IdentityResult>> action, string successMessage, ChangeProfileModel model = null) |
||||
|
{ |
||||
|
var user = await userManager.GetUserAsync(User); |
||||
|
|
||||
|
if (!ModelState.IsValid) |
||||
|
{ |
||||
|
return View("Profile", await GetProfileVM(user, model)); |
||||
|
} |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
var result = await action(user); |
||||
|
|
||||
|
if (result.Succeeded) |
||||
|
{ |
||||
|
return RedirectToAction(nameof(Profile), new { successMessage }); |
||||
|
} |
||||
|
|
||||
|
ViewBag.ErrorMessage = string.Join(". ", result.Errors.Select(x => x.Description)); |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
ViewBag.ErrorMessage = "An unexpected exception occurred."; |
||||
|
} |
||||
|
|
||||
|
return View("Profile", await GetProfileVM(user, model)); |
||||
|
} |
||||
|
|
||||
|
private async Task<ProfileVM> GetProfileVM(IUser user, ChangeProfileModel model = null) |
||||
|
{ |
||||
|
var result = new ProfileVM |
||||
|
{ |
||||
|
Email = user.Email, |
||||
|
DisplayName = user.DisplayName(), |
||||
|
PictureUrl = user.PictureUrl(), |
||||
|
HasPassword = await userManager.HasPasswordAsync(user), |
||||
|
HasPasswordAuth = identityOptions.Value.AllowPasswordAuth |
||||
|
}; |
||||
|
|
||||
|
if (model != null) |
||||
|
{ |
||||
|
SimpleMapper.Map(model, result); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ProfileVM.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Controllers.UI.Profile |
||||
|
{ |
||||
|
public sealed class ProfileVM |
||||
|
{ |
||||
|
public string Email { get; set; } |
||||
|
|
||||
|
public string DisplayName { get; set; } |
||||
|
|
||||
|
public string PictureUrl { get; set; } |
||||
|
|
||||
|
public bool HasPassword { get; set; } |
||||
|
|
||||
|
public bool HasPasswordAuth { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
// ==========================================================================
|
||||
|
// SetPasswordModel.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
|
||||
|
namespace Squidex.Controllers.UI.Profile |
||||
|
{ |
||||
|
public class SetPasswordModel |
||||
|
{ |
||||
|
[Required(ErrorMessage = "Password is required.")] |
||||
|
public string Password { get; set; } |
||||
|
|
||||
|
[Compare(nameof(Password), ErrorMessage = "Passwords must be identitical.")] |
||||
|
public string PasswordConfirm { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -1,9 +0,0 @@ |
|||||
@{ |
|
||||
ViewBag.Title = "Login failed"; |
|
||||
} |
|
||||
|
|
||||
<h1 class="splash-h1">Login failed</h1> |
|
||||
|
|
||||
<p class="splash-text"> |
|
||||
We are really sorry, something went wrong when you tried to login. |
|
||||
</p> |
|
||||
@ -0,0 +1,9 @@ |
|||||
|
@{ |
||||
|
ViewBag.Title = "Operation failed"; |
||||
|
} |
||||
|
|
||||
|
<h1 class="splash-h1">Operation failed</h1> |
||||
|
|
||||
|
<p class="splash-text"> |
||||
|
We are really sorry that something went wrong. |
||||
|
</p> |
||||
@ -0,0 +1,155 @@ |
|||||
|
@using System.Collections.Generic |
||||
|
@using Microsoft.AspNetCore.Http |
||||
|
@using Microsoft.AspNetCore.Http.Authentication |
||||
|
@model Squidex.Controllers.UI.Profile.ProfileVM |
||||
|
|
||||
|
@{ |
||||
|
ViewBag.Title = "Profile"; |
||||
|
} |
||||
|
|
||||
|
<div class="profile profile-lg"> |
||||
|
<img class="profile-logo" src="~/images/logo-small.png" /> |
||||
|
|
||||
|
<div> |
||||
|
<h4 class="profile-title">Welcome, @Model.DisplayName</h4> |
||||
|
</div> |
||||
|
|
||||
|
@if (!string.IsNullOrWhiteSpace(ViewBag.SuccessMessage)) |
||||
|
{ |
||||
|
<div class="form-success"> |
||||
|
@ViewBag.SuccessMessage |
||||
|
</div> |
||||
|
} |
||||
|
|
||||
|
@if (!string.IsNullOrWhiteSpace(ViewBag.ErrorMessage)) |
||||
|
{ |
||||
|
<div class="form-error"> |
||||
|
@ViewBag.ErrorMessage |
||||
|
</div> |
||||
|
} |
||||
|
|
||||
|
<div class="row"> |
||||
|
<div class="col profile-picture-col"> |
||||
|
<img class="profile-picture" src="@Model.PictureUrl" /> |
||||
|
</div> |
||||
|
<div class="col"> |
||||
|
<form class="profile-form" asp-controller="Profile" asp-action="Profile" method="post"> |
||||
|
<div class="form-group"> |
||||
|
<label for="email">Email</label> |
||||
|
|
||||
|
@if (ViewContext.ViewData.ModelState["Email"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid) |
||||
|
{ |
||||
|
<div class="errors-box"> |
||||
|
<span asp-validation-for="Email" class="errors"></span> |
||||
|
</div> |
||||
|
} |
||||
|
|
||||
|
<input type="email" ap class="form-control" asp-for="Email" name="email" id="email" /> |
||||
|
</div> |
||||
|
|
||||
|
<div class="form-group"> |
||||
|
<label for="displayName">Display Name</label> |
||||
|
|
||||
|
|
||||
|
@if (ViewContext.ViewData.ModelState["DisplayName"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid) |
||||
|
{ |
||||
|
<div class="errors-box"> |
||||
|
<span asp-validation-for="DisplayName" class="errors"></span> |
||||
|
</div> |
||||
|
} |
||||
|
|
||||
|
<input type="text" class="form-control" asp-for="DisplayName" name="displayName" id="displayName"/> |
||||
|
</div> |
||||
|
|
||||
|
<button type="submit" class="btn btn-primary">Save</button> |
||||
|
</form> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
@if (Model.HasPasswordAuth) |
||||
|
{ |
||||
|
<h5 class="profile-section">Password</h5> |
||||
|
|
||||
|
@if (Model.HasPassword) |
||||
|
{ |
||||
|
<form class="profile-form" asp-controller="Profile" asp-action="ChangePassword" method="post"> |
||||
|
<div class="form-group"> |
||||
|
<label for="oldPassword">Old Password</label> |
||||
|
|
||||
|
@if (ViewContext.ViewData.ModelState["OldPassword"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid) |
||||
|
{ |
||||
|
<div class="errors-box"> |
||||
|
<span class="errors">@Html.ValidationMessage("OldPassword")</span> |
||||
|
</div> |
||||
|
} |
||||
|
|
||||
|
<input type="password" class="form-control" name="oldPassword" id="oldPassword"/> |
||||
|
</div> |
||||
|
|
||||
|
<div class="form-group"> |
||||
|
<label for="password">Password</label> |
||||
|
|
||||
|
@if (ViewContext.ViewData.ModelState["Password"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid) |
||||
|
{ |
||||
|
<div class="errors-box"> |
||||
|
<span class="errors">@Html.ValidationMessage("Password")</span> |
||||
|
</div> |
||||
|
} |
||||
|
|
||||
|
<input type="password" class="form-control" name="password" id="password"/> |
||||
|
</div> |
||||
|
|
||||
|
<div class="form-group"> |
||||
|
<label for="passwordConfirm">Confirm</label> |
||||
|
|
||||
|
@if (ViewContext.ViewData.ModelState["PasswordConfirm"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid) |
||||
|
{ |
||||
|
<div class="errors-box"> |
||||
|
<span class="errors">@Html.ValidationMessage("PasswordConfirm")</span> |
||||
|
</div> |
||||
|
} |
||||
|
|
||||
|
<input type="password" class="form-control" name="passwordConfirm" id="passwordConfirm"/> |
||||
|
</div> |
||||
|
|
||||
|
<div class="form-group"> |
||||
|
<button type="submit" class="btn btn-primary">Change Password</button> |
||||
|
</div> |
||||
|
</form> |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
<form class="profile-form" asp-controller="Profile" asp-action="SetPassword" method="post"> |
||||
|
<div class="form-group"> |
||||
|
<label for="password">Password</label> |
||||
|
|
||||
|
@if (ViewContext.ViewData.ModelState["Password"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid) |
||||
|
{ |
||||
|
<div class="errors-box"> |
||||
|
<span class="errors">@Html.ValidationMessage("Password")</span> |
||||
|
</div> |
||||
|
} |
||||
|
|
||||
|
<input type="password" class="form-control" name="password" id="password"/> |
||||
|
</div> |
||||
|
|
||||
|
<div class="form-group"> |
||||
|
<label for="passwordConfirm">Confirm</label> |
||||
|
|
||||
|
@if (ViewContext.ViewData.ModelState["PasswordConfirm"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid) |
||||
|
{ |
||||
|
<div class="errors-box"> |
||||
|
<span class="errors">@Html.ValidationMessage("PasswordConfirm")</span> |
||||
|
</div> |
||||
|
} |
||||
|
|
||||
|
<input type="password" class="form-control" name="passwordConfirm" id="passwordConfirm"/> |
||||
|
</div> |
||||
|
|
||||
|
<div class="form-group"> |
||||
|
<button type="submit" class="btn btn-primary">Set Password</button> |
||||
|
</div> |
||||
|
</form> |
||||
|
} |
||||
|
} |
||||
|
</div> |
||||
@ -0,0 +1,23 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Sebastian Stehle. All rights reserved |
||||
|
*/ |
||||
|
|
||||
|
import { Directive, HostListener, Input } from '@angular/core'; |
||||
|
|
||||
|
@Directive({ |
||||
|
selector: '[sqxPopupLink]' |
||||
|
}) |
||||
|
export class PopupLinkDirective { |
||||
|
@Input('sqxPopupLink') |
||||
|
public url: string; |
||||
|
|
||||
|
@HostListener('click') |
||||
|
public onClick(): boolean { |
||||
|
window.open(this.url, '_target', 'location=no,toolbar=no,width=500,height=500,left=100,top=100;'); |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue