Manage mailbox aliases

This commit is contained in:
Robert Marshall 2021-05-29 12:58:17 +01:00
parent 6d289a7920
commit 2bec3d021a
13 changed files with 142 additions and 19 deletions

View file

@ -27,6 +27,8 @@ steps:
from_secret: AuthEndpoint
AuthApiKey:
from_secret: AuthApiKey
MailboxEndpoint:
from_secret: MailboxEndpoint
commands:
- curl -sL https://deb.nodesource.com/setup_12.x | bash -
- apt-get install -y nodejs
@ -37,6 +39,7 @@ steps:
- sed -i "s/<GitEndpoint>/$GitEndpoint/g" output/appsettings.json
- sed -i "s/<AuthEndpoint>/$AuthEndpoint/g" output/appsettings.json
- sed -i "s/<AuthApiKey>/$AuthApiKey/g" output/appsettings.json
- sed -i "s/<MailboxEndpoint>/$MailboxEndpoint/g" output/appsettings.json
- cp Infrastructure/website.service output/
- cp -r ./output/* /output
- name: restart service

View file

@ -5,4 +5,6 @@ sed \
-e 's/"blogApiEndpoint": ".*"/"blogApiEndpoint": ""/g' \
-e 's/"gitApiEndpoint": ".*"/"gitApiEndpoint": ""/g' \
-e 's/"authApiEndpoint": ".*"/"authApiEndpoint": ""/g' \
-e 's/"authApiKey": ".*"/"authApiKey": ""/g' \
-e 's/"mailboxesApiEndpoint": ".*"/"mailboxesApiEndpoint": ""/g' \
$1

View file

@ -20,7 +20,8 @@ namespace Website.Tests.Controllers {
public void Login_WithReturnUrl_ReturnsViewThatHasModelWithReturnUrlAndFalseFailedAttempt() {
var authenticationProvider = Substitute.For<IAuthenticationProvider>();
var apiKeyManager = Substitute.For<IApiKeyManager>();
var controller = new AccountController(authenticationProvider, apiKeyManager);
var mailboxesApi = Substitute.For<IMailboxesApi>();
var controller = new AccountController(authenticationProvider, apiKeyManager, mailboxesApi);
var expected = new LoginViewModel {
ReturnUrl = "returnUrl",
@ -36,7 +37,8 @@ namespace Website.Tests.Controllers {
public void Login_WithReturnUrlAndFailedAttempt_ReturnsViewThatHasModelWithReturnUrlAndTrueFailedAttempt() {
var authenticationProvider = Substitute.For<IAuthenticationProvider>();
var apiKeyManager = Substitute.For<IApiKeyManager>();
var controller = new AccountController(authenticationProvider, apiKeyManager);
var mailboxesApi = Substitute.For<IMailboxesApi>();
var controller = new AccountController(authenticationProvider, apiKeyManager, mailboxesApi);
var expected = new LoginViewModel {
ReturnUrl = "returnUrl",
@ -52,7 +54,8 @@ namespace Website.Tests.Controllers {
public async Task Login_WithLoginRequest_WhenAuthenticationIsUnsuccessful_ReturnsViewThatHasModelWithReturnUrlAndTrueFailedAttempt() {
var authenticationProvider = Substitute.For<IAuthenticationProvider>();
var apiKeyManager = Substitute.For<IApiKeyManager>();
var controller = new AccountController(authenticationProvider, apiKeyManager);
var mailboxesApi = Substitute.For<IMailboxesApi>();
var controller = new AccountController(authenticationProvider, apiKeyManager, mailboxesApi);
var request = new LoginRequest {
Username = "username",
@ -80,7 +83,8 @@ namespace Website.Tests.Controllers {
var authenticationProvider = Substitute.For<IAuthenticationProvider>();
var apiKeyManager = Substitute.For<IApiKeyManager>();
var controller = new AccountController(authenticationProvider, apiKeyManager) {
var mailboxesApi = Substitute.For<IMailboxesApi>();
var controller = new AccountController(authenticationProvider, apiKeyManager, mailboxesApi) {
ControllerContext = new ControllerContext {
HttpContext = new DefaultHttpContext {
RequestServices = serviceProvider
@ -116,7 +120,8 @@ namespace Website.Tests.Controllers {
var authenticationProvider = Substitute.For<IAuthenticationProvider>();
var apiKeyManager = Substitute.For<IApiKeyManager>();
var controller = new AccountController(authenticationProvider, apiKeyManager) {
var mailboxesApi = Substitute.For<IMailboxesApi>();
var controller = new AccountController(authenticationProvider, apiKeyManager, mailboxesApi) {
ControllerContext = new ControllerContext {
HttpContext = new DefaultHttpContext {
RequestServices = serviceProvider
@ -140,7 +145,8 @@ namespace Website.Tests.Controllers {
public async Task Login_WithLoginRequest_WhenAuthenticationServiceFails_ReturnsViewThatHasModelWithReturnUrlAndTrueFailedAttempt() {
var authenticationProvider = Substitute.For<IAuthenticationProvider>();
var apiKeyManager = Substitute.For<IApiKeyManager>();
var controller = new AccountController(authenticationProvider, apiKeyManager);
var mailboxesApi = Substitute.For<IMailboxesApi>();
var controller = new AccountController(authenticationProvider, apiKeyManager, mailboxesApi);
var request = new LoginRequest {Username = "username", Password = "password", ReturnUrl = "returnUrl"};

View file

@ -6,22 +6,26 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Website.Data;
using Website.Models.Auth;
using Website.Models.Mailboxes;
using Website.ViewModels;
namespace Website.Controllers {
public class AccountController:Controller {
private readonly IAuthenticationProvider _authenticationProvider;
private readonly IApiKeyManager _apiKeyManager;
private readonly IMailboxesApi _mailboxesApi;
public AccountController(IAuthenticationProvider authenticationProvider, IApiKeyManager apiKeyManager) {
public AccountController(IAuthenticationProvider authenticationProvider, IApiKeyManager apiKeyManager, IMailboxesApi mailboxesApi) {
_authenticationProvider = authenticationProvider;
_apiKeyManager = apiKeyManager;
_mailboxesApi = mailboxesApi;
}
[Authorize]
public async Task<IActionResult> Index() {
var keys = await _apiKeyManager.List();
var model = new AccountViewModel(keys);
var aliases = await _mailboxesApi.ListAliases();
var model = new AccountViewModel(keys, aliases);
return View(model);
}
@ -79,5 +83,26 @@ namespace Website.Controllers {
await _apiKeyManager.Delete(key);
return RedirectToAction(nameof(Index));
}
public async Task<IActionResult> EnableAlias(int id) {
await _mailboxesApi.EnableAlias(id);
return RedirectToAction(nameof(Index));
}
public async Task<IActionResult> DisableAlias(int id) {
await _mailboxesApi.DisableAlias(id);
return RedirectToAction(nameof(Index));
}
public async Task<IActionResult> DeleteAlias(int id) {
await _mailboxesApi.DeleteAlias(id);
return RedirectToAction(nameof(Index));
}
public async Task<IActionResult> CreateAlias(string source, string destination, bool active) {
var alias = new NewAlias {Source = source, Destination = destination, Active = active};
await _mailboxesApi.CreateAlias(alias);
return RedirectToAction(nameof(Index));
}
}
}

View file

@ -0,0 +1,13 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Website.Models.Mailboxes;
namespace Website.Data {
public interface IMailboxesApi {
Task<IEnumerable<Alias>> ListAliases();
Task EnableAlias(int id);
Task DisableAlias(int id);
Task CreateAlias(NewAlias alias);
Task DeleteAlias(int id);
}
}

View file

@ -0,0 +1,25 @@
using Microsoft.Extensions.Caching.Memory;
using Robware.Lib.ApiClient;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Website.Models.Mailboxes;
namespace Website.Data
{
public class MailboxesApi : ApiClient, IMailboxesApi
{
public MailboxesApi(HttpClient client, IMemoryCache cache, CacheDurations cacheDurations) : base(client, cache, cacheDurations)
{
}
public async Task<IEnumerable<Alias>> ListAliases() => await Get<IEnumerable<Alias>>("/alias/list");
public async Task EnableAlias(int id) => await Patch<object>("/alias/enable", null, new {id});
public async Task DisableAlias(int id) => await Patch<object>("/alias/disable", null, new {id});
public async Task CreateAlias(NewAlias alias) => await Post<object>("/alias/create", alias);
public async Task DeleteAlias(int id) => await Delete<object>("/alias/delete", new {id});
}
}

View file

@ -0,0 +1,9 @@
namespace Website.Models.Mailboxes {
public class Alias {
public int Id { get; set; }
public int DomainId { get; set; }
public string Source { get; set; }
public string Destination { get; set; }
public bool Active { get; set; }
}
}

View file

@ -0,0 +1,7 @@
namespace Website.Models.Mailboxes {
public class NewAlias {
public string Source { get; set; }
public string Destination { get; set; }
public bool Active { get; set; }
}
}

View file

@ -50,6 +50,10 @@ namespace Website
client.BaseAddress = new Uri(Configuration["authApiEndpoint"] + "api/");
client.DefaultRequestHeaders.Add("x-api-key", new[] { Configuration["authApiKey"] });
});
services.AddHttpClient<IMailboxesApi, MailboxesApi>(client => {
client.BaseAddress = new Uri(Configuration["mailboxesApiEndpoint"] + "api/");
client.DefaultRequestHeaders.Add("x-api-key", new[] { Configuration["authApiKey"] });
});
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();

View file

@ -1,12 +1,15 @@
using System.Collections.Generic;
using Website.Models.ApiKey;
using Website.Models.Mailboxes;
namespace Website.ViewModels {
public class AccountViewModel {
public IEnumerable<ApiKey> ApiKeys { get; }
public IEnumerable<Alias> Aliases { get; }
public AccountViewModel(IEnumerable<ApiKey> apiKeys) {
public AccountViewModel(IEnumerable<ApiKey> apiKeys, IEnumerable<Alias> aliases) {
ApiKeys = apiKeys;
Aliases = aliases;
}
}
}

View file

@ -27,4 +27,29 @@
<form asp-action="CreateApiKey">
<label for="new-key-name">Name: </label><input type="text" name="name" id="new-key-name" />
<button type="submit">Create</button>
</form>
<h2>Manage Mailboxes</h2>
<h3>Aliases</h3>
<div class="table-scroll-container">
<table class="actions">
@foreach (var alias in Model.Aliases)
{
<tr>
<td>@alias.Source</td>
<td class="stretch">@alias.Destination</td>
<td>@(alias.Active?"Enabled":"Disabled")</td>
<td><a asp-action="EnableAlias" asp-route-id="@alias.Id"><img src="/images/enable.svg" alt="Enable" title="Enable" onclick="return confirm('Are you sure?')"/></a></td>
<td><a asp-action="DisableAlias" asp-route-id="@alias.Id"><img src="/images/disable.svg" alt="Disable" title="Disable" onclick="return confirm('Are you sure?')"/></a></td>
<td><a asp-action="DeleteAlias" asp-route-id="@alias.Id"><img src="/images/delete.svg" alt="Delete" title="Delete" onclick="return confirm('Are you sure?')"/></a></td>
</tr>
}
</table>
</div>
<h3>Create New Alias</h3>
<form asp-action="CreateAlias">
<label>Source: <input type="text" name="source" /></label>
<label>Destination: <input type="text" name="destination" /></label>
<label>@Html.CheckBox("active") Active</label>
<button type="submit">Create</button>
</form>

View file

@ -6,21 +6,21 @@
"Microsoft": "Information"
}
},
"Kestrel": {
"EndPoints": {
"Http": {
"Url": "https://0.0.0.0:5000"
}
}
},
"blogApiEndpoint": "",
"gitApiEndpoint": "",
"authApiEndpoint": "",
"authApiKey": "",
"mailboxesApiEndpoint": "",
"cacheDurations": {
"default": {
"default": 30
},
"BlogApi": {
"default": 21600, // 6 hours
"GetPostByUrlAsync": 86400, // 1 day
"GetPostByIdAsync": 86400 // 1 day
},
"GitApi": {
"default": 3600, //1 hour
"GetCommit": 86400 // 1 day
"default": 1
}
}
}

View file

@ -8,6 +8,7 @@
"gitApiEndpoint": "<GitEndpoint>",
"authApiEndpoint": "<AuthEndpoint>",
"authApiKey": "<AuthApiKey>",
"mailboxesApiEndpoint": "<MailboxEndpoint>",
"AllowedHosts": "*",
"Kestrel": {
"EndPoints": {