Browse Source

Manage mailbox aliases

Robert Marshall 3 years ago
parent
commit
2bec3d021a

+ 3 - 0
Infrastructure/.drone.yml

@@ -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

+ 2 - 0
clean-config.sh

@@ -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

+ 12 - 6
src/Website.Tests/Controllers/AccountControllerTests.cs

@@ -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"};
 

+ 27 - 2
src/Website/Controllers/AccountController.cs

@@ -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));
+		}
 	}
 }

+ 13 - 0
src/Website/Data/IMailboxesApi.cs

@@ -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);
+	}
+}

+ 25 - 0
src/Website/Data/MailboxesApi.cs

@@ -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});
+	}
+}

+ 9 - 0
src/Website/Models/Mailboxes/Alias.cs

@@ -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; }
+	}
+}

+ 7 - 0
src/Website/Models/Mailboxes/NewAlias.cs

@@ -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; }
+	}
+}

+ 4 - 0
src/Website/Startup.cs

@@ -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();
 

+ 4 - 1
src/Website/ViewModels/AccountViewModel.cs

@@ -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;
 		}
 	}
 }

+ 25 - 0
src/Website/Views/Account/Index.cshtml

@@ -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>

+ 10 - 10
src/Website/appsettings.Development.json

@@ -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
 		}
 	}
 }

+ 1 - 0
src/Website/appsettings.json

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