refactoring the login

This commit is contained in:
Paul Schneider 2026-02-22 19:54:10 +00:00
commit 9289207085
18 changed files with 209 additions and 368 deletions

3
.vscode/launch.json vendored
View file

@ -28,7 +28,8 @@
"justMyCode": false, "justMyCode": false,
"serverReadyAction": { "serverReadyAction": {
"action": "openExternally", "action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)" "pattern": "\\bNow listening on:\\s+(https?://\\S+)",
"killOnServerStop": true
}, },
"env": { "env": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"

View file

@ -3,6 +3,7 @@
"cSpell.words": [ "cSpell.words": [
"appsettings", "appsettings",
"asciidoctor",
"Cratie", "Cratie",
"Newtonsoft", "Newtonsoft",
"Npgsql", "Npgsql",

View file

@ -20,7 +20,7 @@ namespace Yavsc
public const string UserNameRegExp = @"^[a-zA-Z][a-zA-Z0-9._-]*$"; public const string UserNameRegExp = @"^[a-zA-Z][a-zA-Z0-9._-]*$";
public const string UserFileNamePatternRegExp = @"^([a-zA-Z0-9._-]*/)*[a-zA-Z0-9._-]+$"; public const string UserFileNamePatternRegExp = @"^([a-zA-Z0-9._-]*/)*[a-zA-Z0-9._-]+$";
public const string LoginPath = "~/signin"; public const string SigninPath = "~/signin";
public const string LogoutPath = "~/signout"; public const string LogoutPath = "~/signout";
public const string AccessDeniedPath = "~/Account/AccessDenied"; public const string AccessDeniedPath = "~/Account/AccessDenied";

View file

@ -23,6 +23,7 @@ using IdentityServer8.Events;
using IdentityServer8.Extensions; using IdentityServer8.Extensions;
using IdentityModel; using IdentityModel;
using Yavsc.Server.Helpers; using Yavsc.Server.Helpers;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Yavsc.Controllers namespace Yavsc.Controllers
{ {
@ -82,31 +83,134 @@ namespace Yavsc.Controllers
} }
public async Task<IActionResult> SignIn(SignInModel model, [FromForm] string button)
{
if (Request.Method == "POST") // "hGbkk9B94NAae#aG"
{
if (model.Provider == null || model.Provider == "LOCAL")
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByNameAsync(model.UserName);
var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
if (user != null)
{
var signin = await _signInManager.CheckPasswordSignInAsync(user, model.Password, true);
// validate username/password against in-memory store
if (signin.Succeeded)
{
await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id, user.UserName, clientId: context?.Client.ClientId));
// only set explicit expiration here if user chooses "remember me".
// otherwise we rely upon expiration configured in cookie middleware.
await HttpContext.SignInAsync(user, _roleManager, model.RememberMe, _dbContext);
var authResult = await HttpContext.AuthenticateAsync();
if (!authResult.Succeeded)
{
return this.Unauthorized();
}
String bearer = await HttpContext.GetTokenAsync("Bearer", "Bearer");
HttpContext.Response.Cookies.Append("Bearer", bearer);
if (context != null)
{
if (context.IsNativeClient())
{
// The client is native, so this change in how to
// return the response is for better UX for the end user.
return this.LoadingPage("Redirect", model.ReturnUrl);
}
// we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
return Redirect(model.ReturnUrl);
}
// request for a local page
if (Url.IsLocalUrl(model.ReturnUrl))
{
return Redirect(model.ReturnUrl);
}
else if (string.IsNullOrEmpty(model.ReturnUrl))
{
return Redirect("~/");
}
else
{
// user might have clicked on a malicious link - should be logged
throw new Exception("invalid return URL");
}
}
}
await _events.RaiseAsync(new UserLoginFailureEvent(model.UserName, "invalid credentials", clientId: context?.Client.ClientId));
ModelState.AddModelError(string.Empty, AccountOptions.InvalidCredentialsErrorMessage);
}
}
else
{
// Note: the "provider" parameter corresponds to the external
// authentication provider choosen by the user agent.
if (string.IsNullOrEmpty(model.Provider))
{
_logger.LogWarning("Provider not specified");
return BadRequest();
}
// Instruct the middleware corresponding to the requested external identity
// provider to redirect the user agent to its own authorization endpoint.
// Note: the authenticationScheme parameter must match the value configured in Startup.cs
// Note: the "returnUrl" parameter corresponds to the endpoint the user agent
// will be redirected to after a successful authentication and not
// the redirect_uri of the requesting client application.
if (string.IsNullOrEmpty(model.ReturnUrl))
{
_logger.LogWarning("ReturnUrl not specified");
return BadRequest();
}
// Note: this still is not the redirect uri given to the third party provider, at building the challenge.
var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { model.ReturnUrl }, protocol: "https", host: Config.Authority);
var properties = _signInManager.ConfigureExternalAuthenticationProperties(model.Provider, redirectUrl);
// var properties = new AuthenticationProperties{RedirectUri=ReturnUrl};
return new ChallengeResult(model.Provider, properties);
}
}
return View(model);
}
/// <summary> /// <summary>
/// Entry point into the login workflow /// Entry point into the login workflow
/// </summary> /// </summary>
[HttpGet] [HttpGet(Constants.SigninPath)]
public async Task<IActionResult> Login(string returnUrl) public async Task<IActionResult> Signin(SignInModel model)
{ {
// build a model so we know what to show on the login page // build a model so we know what to show on the login page
var vm = await BuildLoginViewModelAsync(returnUrl); var vm = await BuildLoginViewModelAsync(model);
if (vm.IsExternalLoginOnly) if (vm.IsExternalLoginOnly)
{ {
// we only have one option for logging in and it's an external provider // we only have one option for logging in and it's an external provider
return RedirectToAction("Challenge", "External", new { scheme = vm.ExternalLoginScheme, returnUrl }); return RedirectToAction("Challenge", "External", new { scheme = vm.ExternalLoginScheme, model.ReturnUrl });
} }
ModelState.Clear();
return View("Signin", vm); return View("Signin", vm);
} }
/// <summary> /// <summary>
/// Handle postback from username/password login /// Handle postback from username/password login
/// </summary> /// </summary>
[HttpPost(Constants.LoginPath)] ///
[HttpPost(Constants.SigninPath)]
[ValidateAntiForgeryToken] [ValidateAntiForgeryToken]
[AllowAnonymous] [AllowAnonymous]
public async Task<IActionResult> Login([FromForm] SignInModel model, [FromForm] string button)
public async Task<IActionResult> Signin([FromForm] SignInModel model, [FromForm] string button)
{ {
// check if we are in the context of an authorization request // check if we are in the context of an authorization request
@ -273,28 +377,25 @@ namespace Yavsc.Controllers
/*****************************************/ /*****************************************/
/* helper APIs for the AccountController */ /* helper APIs for the AccountController */
/*****************************************/ /*****************************************/
private async Task<SignInModel> BuildLoginViewModelAsync(string returnUrl) private async Task<SignInModel> BuildLoginViewModelAsync(SignInModel model)
{ {
var context = await _interaction.GetAuthorizationContextAsync(returnUrl); var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
if (context?.IdP != null && await _schemeProvider.GetSchemeAsync(context.IdP) != null) if (context?.IdP != null && await _schemeProvider.GetSchemeAsync(context.IdP) != null)
{ {
var local = context.IdP == IdentityServer8.IdentityServerConstants.LocalIdentityProvider; var local = context.IdP == IdentityServer8.IdentityServerConstants.LocalIdentityProvider;
// this is meant to short circuit the UI and only trigger the one external IdP // this is meant to short circuit the UI and only trigger the one external IdP
var vm = new SignInModel
{ model.EnableLocalLogin = local;
EnableLocalLogin = local, model.UserName = context?.LoginHint;
ReturnUrl = returnUrl, model.IsExternalLoginOnly = false;
UserName = context?.LoginHint,
IsExternalLoginOnly = false
};
if (!local) if (!local)
{ {
vm.ExternalProviders = new[] { new ExternalProvider { AuthenticationScheme = context.IdP } }; model.ExternalProviders = new[] { new ExternalProvider { AuthenticationScheme = context.IdP } };
} }
return vm; return model;
} }
var schemes = await _schemeProvider.GetAllSchemesAsync(); var schemes = await _schemeProvider.GetAllSchemesAsync();
@ -322,22 +423,12 @@ namespace Yavsc.Controllers
} }
} }
return new SignInModel model.RememberMe = AccountOptions.AllowRememberLogin;
{ model.EnableLocalLogin = allowLocal && AccountOptions.AllowLocalLogin;
RememberMe = AccountOptions.AllowRememberLogin, model.UserName = context?.LoginHint;
EnableLocalLogin = allowLocal && AccountOptions.AllowLocalLogin, model.ExternalProviders = providers.ToArray();
ReturnUrl = returnUrl,
UserName = context?.LoginHint,
ExternalProviders = providers.ToArray()
};
}
private async Task<SignInModel> BuildLoginViewModelAsync(SignInModel model) return model;
{
var vm = await BuildLoginViewModelAsync(model.ReturnUrl);
vm.UserName = model.UserName;
vm.RememberMe = model.RememberMe;
return vm;
} }
private async Task<LogoutViewModel> BuildLogoutViewModelAsync(string logoutId) private async Task<LogoutViewModel> BuildLogoutViewModelAsync(string logoutId)
@ -432,28 +523,6 @@ namespace Yavsc.Controllers
return System.Guid.NewGuid().ToString(); return System.Guid.NewGuid().ToString();
} }
[AllowAnonymous]
[HttpGet(Constants.LoginPath)]
public ActionResult SignIn(string returnUrl = null)
{
// Note: the "returnUrl" parameter corresponds to the endpoint the user agent
// will be redirected to after a successful authentication and not
// the redirect_uri of the requesting client application against the third
// party identity provider.
return View(new SignInModel
{
ReturnUrl = returnUrl ?? "/",
ExternalProviders = [],
EnableLocalLogin = true
});
/*
Note: When using an external login provider, redirect the query :
var properties = _signInManager.ConfigureExternalAuthenticationProperties(OpenIdConnectDefaults.AuthenticationScheme, returnUrl);
return new ChallengeResult(OpenIdConnectDefaults.AuthenticationScheme, properties);
*/
}
[AllowAnonymous] [AllowAnonymous]
public ActionResult AccessDenied(string requestUrl = null) public ActionResult AccessDenied(string requestUrl = null)
{ {
@ -466,106 +535,7 @@ namespace Yavsc.Controllers
return View("AccessDenied", requestUrl); return View("AccessDenied", requestUrl);
} }
public async Task<IActionResult> SignIn(SignInModel model)
{
if (Request.Method == "POST") // "hGbkk9B94NAae#aG"
{
if (model.Provider == null || model.Provider == "LOCAL")
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByNameAsync(model.UserName);
var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
if (user != null)
{
var signin = await _signInManager.CheckPasswordSignInAsync(user, model.Password, true);
// validate username/password against in-memory store
if (signin.Succeeded)
{
await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id, user.UserName, clientId: context?.Client.ClientId));
// only set explicit expiration here if user chooses "remember me".
// otherwise we rely upon expiration configured in cookie middleware.
await HttpContext.SignInAsync(user, _roleManager, model.RememberMe, _dbContext);
var authResult = await HttpContext.AuthenticateAsync();
if (!authResult.Succeeded)
{
return this.Unauthorized();
}
String bearer = await HttpContext.GetTokenAsync("Bearer", "Bearer");
HttpContext.Response.Cookies.Append("Bearer", bearer);
if (context != null)
{
if (context.IsNativeClient())
{
// The client is native, so this change in how to
// return the response is for better UX for the end user.
return this.LoadingPage("Redirect", model.ReturnUrl);
}
// we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
return Redirect(model.ReturnUrl);
}
// request for a local page
if (Url.IsLocalUrl(model.ReturnUrl))
{
return Redirect(model.ReturnUrl);
}
else if (string.IsNullOrEmpty(model.ReturnUrl))
{
return Redirect("~/");
}
else
{
// user might have clicked on a malicious link - should be logged
throw new Exception("invalid return URL");
}
}
}
await _events.RaiseAsync(new UserLoginFailureEvent(model.UserName, "invalid credentials", clientId: context?.Client.ClientId));
ModelState.AddModelError(string.Empty, AccountOptions.InvalidCredentialsErrorMessage);
}
}
else
{
// Note: the "provider" parameter corresponds to the external
// authentication provider choosen by the user agent.
if (string.IsNullOrEmpty(model.Provider))
{
_logger.LogWarning("Provider not specified");
return BadRequest();
}
// Instruct the middleware corresponding to the requested external identity
// provider to redirect the user agent to its own authorization endpoint.
// Note: the authenticationScheme parameter must match the value configured in Startup.cs
// Note: the "returnUrl" parameter corresponds to the endpoint the user agent
// will be redirected to after a successful authentication and not
// the redirect_uri of the requesting client application.
if (string.IsNullOrEmpty(model.ReturnUrl))
{
_logger.LogWarning("ReturnUrl not specified");
return BadRequest();
}
// Note: this still is not the redirect uri given to the third party provider, at building the challenge.
var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { model.ReturnUrl }, protocol: "https", host: Config.Authority);
var properties = _signInManager.ConfigureExternalAuthenticationProperties(model.Provider, redirectUrl);
// var properties = new AuthenticationProperties{RedirectUri=ReturnUrl};
return new ChallengeResult(model.Provider, properties);
}
}
return View(model);
}
// //
// GET: /Account/Register // GET: /Account/Register

View file

@ -2,13 +2,9 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Yavsc.Models; using Yavsc.Models;
using Yavsc.ViewModels.Auth;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
using Yavsc.Models.Blog; using Yavsc.Models.Blog;
using Yavsc.Helpers;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.EntityFrameworkCore;
using Yavsc.ViewModels.Blog;
using Yavsc.Server.Exceptions; using Yavsc.Server.Exceptions;
using Yavsc.Server.Helpers; using Yavsc.Server.Helpers;
@ -97,22 +93,23 @@ namespace Yavsc.Controllers
[Authorize()] [Authorize()]
public IActionResult Create(string title) public IActionResult Create(string title)
{ {
var result = new BlogPostCreateViewModel var result = new BlogPostEditViewModel
(new BlogPost
{ {
Title = title Title = title
}; }, true);
SetLangItems(); SetLangItems();
return View(result); return View(result);
} }
// POST: Blog/Create // POST: Blog/Create
[HttpPost, Authorize, ValidateAntiForgeryToken] [HttpPost, Authorize, ValidateAntiForgeryToken]
public IActionResult Create(BlogPostEditViewModel blogInput) public IActionResult Create(BlogPost blogInput)
{ {
if (ModelState.IsValid) if (ModelState.IsValid)
{ {
BlogPost post = blogSpotService.Create(User.GetUserId(), BlogPost post = blogSpotService.Create(User.GetUserId(),
BlogPostEditViewModel.FromViewModel(blogInput)); blogInput);
return RedirectToAction("Index"); return RedirectToAction("Index");
} }
return View("Edit", blogInput); return View("Edit", blogInput);
@ -133,6 +130,8 @@ namespace Yavsc.Controllers
{ {
return NotFound(); return NotFound();
} }
SetLangItems(); SetLangItems();
return View(blog); return View(blog);

View file

@ -1,5 +1,3 @@
using System.Diagnostics; using System.Diagnostics;
using System.Security.Claims; using System.Security.Claims;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -11,14 +9,12 @@ using Yavsc.Models.Blog;
using Yavsc.Server.Exceptions; using Yavsc.Server.Exceptions;
using Yavsc.Server.Helpers; using Yavsc.Server.Helpers;
using Yavsc.ViewModels.Auth; using Yavsc.ViewModels.Auth;
using Yavsc.ViewModels.Blog;
public class BlogSpotService public class BlogSpotService
{ {
private readonly ApplicationDbContext _context; private readonly ApplicationDbContext _context;
private readonly IAuthorizationService _authorizationService; private readonly IAuthorizationService _authorizationService;
public BlogSpotService(ApplicationDbContext context, public BlogSpotService(ApplicationDbContext context,
IAuthorizationService authorizationService) IAuthorizationService authorizationService)
{ {
@ -26,15 +22,8 @@ public class BlogSpotService
_context = context; _context = context;
} }
public BlogPost Create(string userId, BlogPostEditViewModel blogInput) public BlogPost Create(string userId, BlogPost post)
{ {
BlogPost post = new BlogPost
{
Title = blogInput.Title,
Content = blogInput.Content,
Photo = blogInput.Photo,
AuthorId = userId
};
_context.BlogSpot.Add(post); _context.BlogSpot.Add(post);
_context.SaveChanges(userId); _context.SaveChanges(userId);
return post; return post;
@ -42,17 +31,18 @@ public class BlogSpotService
public async Task<BlogPostEditViewModel> GetPostForEdition(ClaimsPrincipal user, long blogPostId) public async Task<BlogPostEditViewModel> GetPostForEdition(ClaimsPrincipal user, long blogPostId)
{ {
var blog = await _context.BlogSpot.Include(x => x.Author).Include(x => x.ACL).SingleAsync(m => m.Id == blogPostId); var blog = await _context.BlogSpot.Include(x => x.Author).Include(x => x.ACL).SingleAsync(m => m.Id == blogPostId);
var auth = await _authorizationService.AuthorizeAsync(user, blog, new EditPermission()); var auth = await _authorizationService.AuthorizeAsync(user, blog, new EditPermission());
if (!auth.Succeeded) if (!auth.Succeeded)
{ {
throw new AuthorizationFailureException(auth); throw new AuthorizationFailureException(auth);
} }
return BlogPostEditViewModel.From(blog); var pub = await _context.blogSpotPublications.AnyAsync(x => x.BlogpostId == blog.Id);
return new BlogPostEditViewModel(blog, pub);
} }
public async Task<BlogPost> Details(ClaimsPrincipal user, long blogPostId) public async Task<BlogPost> Details(ClaimsPrincipal user, long blogPostId)
{ {
BlogPost blog = await _context.BlogSpot BlogPost blog = await _context.BlogSpot
.Include(p => p.Author) .Include(p => p.Author)
.Include(p => p.Tags) .Include(p => p.Tags)
@ -91,7 +81,7 @@ public class BlogSpotService
// saves the change // saves the change
_context.Update(blog); _context.Update(blog);
var publication = await _context.blogSpotPublications.SingleOrDefaultAsync var publication = await _context.blogSpotPublications.SingleOrDefaultAsync
(p=>p.BlogpostId==blogEdit.Id); (p => p.BlogpostId == blogEdit.Id);
if (publication != null) if (publication != null)
{ {
if (!blogEdit.Publish) if (!blogEdit.Publish)

View file

@ -2,7 +2,7 @@
<div class="login-page"> <div class="login-page">
<div class="lead"> <div class="lead">
<h1>Login</h1> <h1>Signin</h1>
<p>Choose how to login</p> <p>Choose how to login</p>
</div> </div>
@ -19,7 +19,7 @@
</div> </div>
<div class="card-body"> <div class="card-body">
<form asp-route="Login"> <form asp-route="Signin">
<input type="hidden" asp-for="ReturnUrl" /> <input type="hidden" asp-for="ReturnUrl" />
<div class="form-group"> <div class="form-group">
@ -84,4 +84,4 @@
</div> </div>
} }
</div> </div>
</div> </div>

View file

@ -2,7 +2,7 @@
<div class="login-page"> <div class="login-page">
<div class="lead"> <div class="lead">
<h1>Login</h1> <h1>Signin</h1>
<p>Choose how to login</p> <p>Choose how to login</p>
</div> </div>
@ -19,7 +19,7 @@
</div> </div>
<div class="card-body"> <div class="card-body">
<form asp-action="Login" method="POST" asp-controller="Account"> <form asp-action="Signin" method="POST" asp-controller="Account">
<input type="hidden" asp-for="ReturnUrl" /> <input type="hidden" asp-for="ReturnUrl" />
<div class="form-group"> <div class="form-group">

View file

@ -1,4 +1,4 @@
@model BlogPostCreateViewModel @model BlogPostEditViewModel
@{ @{
ViewData["Title"] = "Blog post edition"; ViewData["Title"] = "Blog post edition";

View file

@ -1,5 +1,4 @@
@model BlogPostEditViewModel @model BlogPostEditViewModel
@{ @{
ViewData["Title"] = "Blog post edition"; ViewData["Title"] = "Blog post edition";
} }
@ -55,7 +54,6 @@
<h2 title="Titre du post" class="blogtitle" id="titleview" >@Model.Title</h2> <h2 title="Titre du post" class="blogtitle" id="titleview" >@Model.Title</h2>
<div title="Contenu du post" id="contentview"><asciidoc>@Model.Content</asciidoc></div>
<hr> <hr>
<form asp-action="Edit"> <form asp-action="Edit">
@ -84,7 +82,8 @@
<div class="form-group mdcoding"> <div class="form-group mdcoding">
<label asp-for="Content" class="col-md-2 control-label" ></label> <label asp-for="Content" class="col-md-2 control-label" ></label>
<div class="col-md-10"> <div class="col-md-10">
<textarea asp-for="Content" class="form-control" id="Content" data-from="contentview"> <textarea asp-for="Content" class="form-control"
id="Content" data-from="contentview">
</textarea> </textarea>
<span asp-validation-for="Content" class="text-danger" > <span asp-validation-for="Content" class="text-danger" >
</span> </span>
@ -112,6 +111,14 @@
</div> </div>
</form> </form>
@await Component.InvokeAsync("Directory","") @await Component.InvokeAsync("Directory","")
<hr />
<div id="prev">
<asciidoc>@Model.Content</asciidoc>
</div>
<div > <div >
@{ await Html.RenderPartialAsync("_PostFilesPartial"); } @{ await Html.RenderPartialAsync("_PostFilesPartial"); }
</div> </div>

View file

@ -1,58 +0,0 @@

@using Yavsc.ViewModels.Account
@model SignInModel
@{
ViewData["Title"] = "Log in";
}
<div class="jumbotron">
<h1>@ViewData["Title"]</h1>
<hr/>
<h2 class="lead text-left">Use a local account to log in</h2>
<form asp-action="SignIn" class="form-horizontal" role="form">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label for="EMail" class="col-md-2 control-label">Username</label>
<div class="col-md-10">
<input asp-for="UserName" class="form-control" autocomplete="email" aria-required="true" placeholder="UserName" />
<span asp-validation-for="UserName" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label for="Password" class="col-md-2 control-label">Password</label>
<div class="col-md-10">
<input asp-for="Password" class="form-control" autocomplete="current-password" aria-required="true" placeholder="password" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<div class="checkbox">
<input asp-for="RememberMe" />
<label for="RememberMe" class="control-label">Remember me</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-light btn-success" name="submit.Signin">Login</button>
</div>
</div>
<p>
<a asp-action="Register" asp-controller="Account">Register as a new user?</a>
</p>
<p>
<a asp-action="ForgotPassword" asp-controller="Account">Forgot your password</a>
</p>
<input type="hidden" name="Provider" value="LOCAL" />
<input type="hidden" name="ReturnUrl" value="@Model.ReturnUrl" />
@Html.AntiForgeryToken()
</form>
<hr/>
<h2 class="lead text-left">Use another service to log in:</h2>
</div>

View file

@ -52,7 +52,7 @@ else
<a class="nav-link" asp-controller="Account" asp-action="Register" >Register</a> <a class="nav-link" asp-controller="Account" asp-action="Register" >Register</a>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<a class="nav-link" asp-controller="Account" asp-action="Signin" asp-route-ReturnUrl="~/" >Signin</a> <a class="nav-link" asp-controller="Account" asp-action="Signin" asp-route-ReturnUrl="~/" asp-route-AllowRememberLogin="true" >Signin</a>
</li> </li>
} }

View file

@ -14,7 +14,6 @@
@using Yavsc.Abstract.Models.Messaging; @using Yavsc.Abstract.Models.Messaging;
@using Yavsc.Billing; @using Yavsc.Billing;
@using Yavsc.Server.Models.Calendar; @using Yavsc.Server.Models.Calendar;
@using Yavsc.ViewModels.Blog;
@using Yavsc.ViewModels.Haircut; @using Yavsc.ViewModels.Haircut;
@using Yavsc.ViewModels.Administration; @using Yavsc.ViewModels.Administration;
@using Yavsc.ViewModels.Account; @using Yavsc.ViewModels.Account;

View file

@ -1,63 +1,85 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json; using Newtonsoft.Json;
using Yavsc;
using Yavsc.Abstract.Identity; using Yavsc.Abstract.Identity;
using Yavsc.Abstract.Identity.Security; using Yavsc.Abstract.Identity.Security;
using Yavsc.Attributes.Validation;
using Yavsc.Interfaces; using Yavsc.Interfaces;
using Yavsc.Models.Access; using Yavsc.Models.Access;
using Yavsc.Models.Relationship; using Yavsc.Models.Relationship;
using Yavsc.ViewModels.Blog;
namespace Yavsc.Models.Blog namespace Yavsc.Models.Blog
{ {
public class BlogPost : BlogPostBase, public class BlogPostEditViewModel : BlogPost
IBlogPost, ICircleAuthorized, ITaggable<long> {
public BlogPostEditViewModel(BlogPost post, bool publish)
{
Id = post.Id;
AuthorId = post.AuthorId;
ACL = post.ACL;
Content = post.Content;
Title = post.Title;
Publish = publish;
}
public bool Publish { get; set; }
}
public class BlogPost :
IBlogPost, ICircleAuthorized, ITaggable<long>
{ {
[Key(), DatabaseGenerated(DatabaseGeneratedOption.Identity)] [Key(), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Display(Name="Identifiant du post")] [Display(Name = "Identifiant du post")]
public long Id { get; set; } public long Id { get; set; }
[Display(Name="Identifiant de l'auteur")] [StringLength(1024)]
public string? Photo { get; set; }
[StringLength(1024)]
[Required]
public string Title { get; set; }
[StringLength(56224)]
public string? Content { get; set; }
[InverseProperty("Target")]
[Display(Name = "Liste de contrôle d'accès")]
public virtual List<CircleAuthorizationToBlogPost>? ACL { get; set; }
[Display(Name = "Identifiant de l'auteur")]
[ForeignKey("Author")] [ForeignKey("Author")]
public string? AuthorId { get; set; } public string? AuthorId { get; set; }
[Display(Name="Auteur")] [Display(Name = "Auteur")]
public virtual ApplicationUser? Author { set; get; } public virtual ApplicationUser? Author { set; get; }
[Display(Name="Date de création")] [Display(Name = "Date de création")]
public DateTime DateCreated public DateTime DateCreated
{ {
get; set; get; set;
} }
[Display(Name="Créateur")] [Display(Name = "Créateur")]
public string? UserCreated public string? UserCreated
{ {
get; set; get; set;
} }
[Display(Name="Dernière modification")] [Display(Name = "Dernière modification")]
public DateTime DateModified public DateTime DateModified
{ {
get; set; get; set;
} }
[Display(Name="Utilisateur ayant modifé le dernier")] [Display(Name = "Utilisateur ayant modifé le dernier")]
public string? UserModified public string? UserModified
{ {
get; set; get; set;
} }
public bool AuthorizeCircle(long circleId) public bool AuthorizeCircle(long circleId)
{ {
return ACL?.Any( i=>i.CircleId == circleId) ?? true; return ACL?.Any(i => i.CircleId == circleId) ?? true;
} }
public ICircleAuthorization[] GetACL() public ICircleAuthorization[] GetACL()
@ -67,26 +89,26 @@ namespace Yavsc.Models.Blog
public void Tag(Tag tag) public void Tag(Tag tag)
{ {
var existent = Tags.SingleOrDefault(t => t.PostId == Id && t.TagId == tag.Id); var existent = Tags.SingleOrDefault(t => t.PostId == Id && t.TagId == tag.Id);
if (existent==null) Tags.Add(new BlogTag { PostId = Id, Tag = tag } ); if (existent == null) Tags.Add(new BlogTag { PostId = Id, Tag = tag });
} }
public void DeTag(Tag tag) public void DeTag(Tag tag)
{ {
var existent = Tags.SingleOrDefault(t => (( t.TagId == tag.Id) && t.PostId == Id)); var existent = Tags.SingleOrDefault(t => ((t.TagId == tag.Id) && t.PostId == Id));
if (existent!=null) Tags.Remove(existent); if (existent != null) Tags.Remove(existent);
} }
public string[] GetTags() public string[] GetTags()
{ {
return Tags.Select(t=>t.Tag.Name).ToArray(); return Tags.Select(t => t.Tag.Name).ToArray();
} }
[InverseProperty("Post")] [InverseProperty("Post")]
public virtual List<BlogTag> Tags { get; set; } public virtual List<BlogTag> Tags { get; set; }
[InverseProperty("Post")] [InverseProperty("Post")]
public virtual List<Comment> Comments { get; set; } public virtual List<Comment> Comments { get; set; }
IApplicationUser IBlogPost.Author { get => this.Author; } IApplicationUser IBlogPost.Author { get => this.Author; }
} }

View file

@ -1,13 +0,0 @@
using Yavsc.Models.Blog;
public class BlogPostEdition
{
public string Content { get; internal set; }
public string Title { get; internal set; }
public string Photo { get; internal set; }
internal static BlogPostEdition From(BlogPost blog)
{
throw new NotImplementedException();
}
}

View file

@ -40,7 +40,6 @@ namespace Yavsc.ViewModels.Account
/// <returns></returns> /// <returns></returns>
public string? Provider { get; set; } public string? Provider { get; set; }
/// <summary> /// <summary>
/// This value does NOT indicate the OAuth client method recieving the code, /// This value does NOT indicate the OAuth client method recieving the code,
/// but the one called once authorized. /// but the one called once authorized.

View file

@ -1,26 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Yavsc.Models.Access;
namespace Yavsc.ViewModels.Blog
{
public class BlogPostBase
{
[StringLength(1024)]
public string? Photo { get; set; }
[StringLength(1024)]
[Required]
public string Title { get; set; }
[StringLength(56224)]
public string? Content { get; set; }
[InverseProperty("Target")]
[Display(Name = "Liste de contrôle d'accès")]
public virtual List<CircleAuthorizationToBlogPost>? ACL { get; set; }
}
}

View file

@ -1,50 +0,0 @@
using System.ComponentModel.DataAnnotations;
using Yavsc.Models.Blog;
namespace Yavsc.ViewModels.Blog;
public class BlogPostCreateViewModel : BlogPostBase
{
public bool Publish { get; set; }
}
public class BlogPostEditViewModel : BlogPostCreateViewModel
{
[Required]
public required long Id { get; set; }
public BlogPostEditViewModel()
{
}
public static BlogPostEditViewModel From(BlogPost blogInput)
{
return new BlogPostEditViewModel
{
Id = blogInput.Id,
Title = blogInput.Title,
Publish = false,
Photo = blogInput.Photo,
Content = blogInput.Content,
ACL = blogInput.ACL
};
}
public static BlogPostEditViewModel FromViewModel(BlogPostEditViewModel blogInput)
{
return new BlogPostEditViewModel
{
Id = blogInput.Id,
Title = blogInput.Title,
Publish = false,
Photo = blogInput.Photo,
Content = blogInput.Content,
ACL = blogInput.ACL
};
}
}