#region license // This file is part of Vocaluxe. // // Vocaluxe is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Vocaluxe is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Vocaluxe. If not, see . #endregion using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using VocaluxeWeb.Contracts; using VocaluxeWeb.Dispatching; using VocaluxeWeb.Remote; using VocaluxeWeb.Services; namespace VocaluxeWeb.Api { /// Logging in by picking a profile ("Who are you?"), with PIN if the profile has one. [Route("api/v1/session")] public sealed class SessionController : ApiControllerBase { private readonly IVocaluxeRemote _Remote; private readonly CMainThreadDispatcher _Dispatcher; private readonly CSessionStore _Sessions; private readonly CWebSettingsStore _Settings; public SessionController(IVocaluxeRemote remote, CMainThreadDispatcher dispatcher, CSessionStore sessions, CWebSettingsStore settings) { _Remote = remote; _Dispatcher = dispatcher; _Sessions = sessions; _Settings = settings; } /// The current session. [HttpGet] [RequireSession] public async Task> Get() { var session = CurrentSession; var profile = (await _Dispatcher.InvokeAsync(() => _Remote.GetProfiles())).FirstOrDefault(p => p.Id == session.ProfileId); if (profile == null || !profile.IsActive) { _Sessions.Remove(HttpContext); return Problem(StatusCodes.Status401Unauthorized, "Please log in."); } session.Role = profile.Role; session.ProfileName = profile.Name; return CreateDto(profile, _Settings.Current); } /// Logs in as the given profile. [HttpPost] [ProducesResponseType(typeof(SessionDto), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status403Forbidden)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status429TooManyRequests)] public async Task> Create([FromBody] SessionCreateDto request) { if (_Sessions.IsLocked(request.ProfileId.Value)) { return Problem(StatusCodes.Status429TooManyRequests, "Too many wrong PINs, please wait a moment."); } var profile = (await _Dispatcher.InvokeAsync(() => _Remote.GetProfiles())).FirstOrDefault(p => p.Id == request.ProfileId.Value); if (profile == null || !profile.IsActive) { return Problem(StatusCodes.Status404NotFound, "Profile not found."); } if (profile.PinHash != null) { if (!CPinHasher.Verify(request.Pin ?? "", profile.PinHash, profile.PinSalt)) { _Sessions.RegisterFailedLogin(profile.Id); return Problem(StatusCodes.Status403Forbidden, "Wrong PIN."); } if (CPinHasher.NeedsUpgrade(profile.PinSalt)) { CPinHasher.Hash(request.Pin, out var hash, out var salt); await _Dispatcher.InvokeAsync(() => _Remote.UpdateProfile(profile.Id, new ProfileChange { PinHash = hash, PinSalt = salt })); } } _Sessions.Create(HttpContext, profile.Id, profile.Name, profile.Role); return CreateDto(profile, _Settings.Current); } /// Logs out this device. [HttpDelete] [ProducesResponseType(StatusCodes.Status204NoContent)] public IActionResult Delete() { _Sessions.Remove(HttpContext); return NoContent(); } internal static SessionDto CreateDto(RemoteProfile profile, CWebSettings settings) { return new SessionDto { Profile = ToDto(profile), Permissions = settings.GetPermissions(profile.Role).ToList() }; } } }