diff --git a/.gitignore b/.gitignore index 7b5a9d41..e4a8ecf3 100644 --- a/.gitignore +++ b/.gitignore @@ -349,3 +349,6 @@ Vault/wwwroot/js/dist/ # Development settings appsettings.Development.json + +# Database releases +!db/releases diff --git a/Vault.ruleset b/Vault.ruleset index 0445ad3a..62cf857a 100644 --- a/Vault.ruleset +++ b/Vault.ruleset @@ -1,7 +1,7 @@  - - - + + + diff --git a/Vault/Controllers/CredentialsController.cs b/Vault/Controllers/CredentialsController.cs index e5e3989a..2987d8cd 100644 --- a/Vault/Controllers/CredentialsController.cs +++ b/Vault/Controllers/CredentialsController.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Dapper; using Microsoft.AspNetCore.Mvc; @@ -14,12 +16,45 @@ public class CredentialsController : Controller public CredentialsController(IConnectionFactory cf) => _db = new SqlExecutor(cf); + public async Task ReadTagIndex(string userId) => + await _db.ResultAsJson(async conn => { + var reader = await conn.QueryMultipleAsync(SqlStatements.TagIndex, new { UserID = userId }); + + var tags = await reader.ReadAsync<(string TagID, string Label)>(); + var index = await reader.ReadAsync<(string TagID, string CredentialID)>(); + + return new { + tags = tags.Select(t => new { + t.TagID, + t.Label, + }), + index = index + .GroupBy(i => i.TagID) + .ToDictionary(g => g.Key, g => g.Select(i => i.CredentialID)) + }; + }); + [HttpPost] public async Task Create([FromBody] Credential model) => - await _db.ResultAsJson(conn => conn.ExecuteAsync(SqlStatements.Insert, model.WithNewID())); + await _db.ResultAsJson(async conn => { + var c = model.WithNewID(); + var a = await conn.ExecuteAsync(SqlStatements.Insert, c); + var b = await conn.ExecuteAsync(SqlStatements.TagsToCredential, c.TagArray); + return a + b; + }); public async Task Read(string id) => - await _db.ResultAsJson(conn => conn.QuerySingleOrDefaultAsync(SqlStatements.SelectSingle, new { CredentialID = id })); + await _db.ResultAsJson(async conn => { + var reader = await conn.QueryMultipleAsync(SqlStatements.SelectSingle, new { CredentialID = id }); + + var credential = await reader.ReadSingleAsync(); + var tags = await reader.ReadAsync<(string TagID, string Label)>(); + + credential.Tags = string.Join('|', tags.Select(t => t.TagID)); + credential.TagDisplay = string.Join('|', tags.Select(t => t.Label)); + + return credential; + }); public async Task ReadAll(string userId) => await _db.ResultAsJson(conn => conn.QueryAsync(SqlStatements.Select, new { UserID = userId })); @@ -29,7 +64,12 @@ public async Task ReadSummaries(string userId) => [HttpPost] public async Task Update([FromBody] Credential model) => - await _db.ResultAsJson(conn => conn.ExecuteAsync(SqlStatements.Update, model)); + await _db.ResultAsJson(async conn => { + var a = await conn.ExecuteAsync(SqlStatements.Update, model); + var b = await conn.ExecuteAsync(SqlStatements.DeleteTagsFromCredential, model); + var c = await conn.ExecuteAsync(SqlStatements.TagsToCredential, model.TagArray); + return a + b + c; + }); [HttpPost] public async Task Import([FromBody] ImportViewModel model) => diff --git a/Vault/Models/Credential.cs b/Vault/Models/Credential.cs index af961469..d47d24d7 100644 --- a/Vault/Models/Credential.cs +++ b/Vault/Models/Credential.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using System.Linq; namespace Vault.Models { @@ -19,6 +20,7 @@ public Credential() UserDefined2 = "{{UserDefined2}}"; Notes = "{{Notes}}"; PwdOptions = "{{PwdOptions}}"; + Tags = "{{Tags}}"; } public string CredentialID { get; set; } @@ -50,6 +52,15 @@ public Credential() public string PwdOptions { get; set; } + public string Tags { get; set; } + + public string TagDisplay { get; set; } + + public object[] TagArray => + Tags? + .Split('|', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Select(t => new { TagID = t, CredentialID }).ToArray() ?? []; + public Credential WithNewID() { var credentialWithGuid = (Credential)MemberwiseClone(); diff --git a/Vault/Models/CredentialSummary.cs b/Vault/Models/CredentialSummary.cs index 85b5ddf0..a9e74a2d 100644 --- a/Vault/Models/CredentialSummary.cs +++ b/Vault/Models/CredentialSummary.cs @@ -4,8 +4,6 @@ public class CredentialSummary { public string CredentialID { get; set; } - public string UserID { get; set; } - public string Description { get; set; } public string Username { get; set; } diff --git a/Vault/Models/DatabaseType.cs b/Vault/Models/DatabaseType.cs new file mode 100644 index 00000000..62916905 --- /dev/null +++ b/Vault/Models/DatabaseType.cs @@ -0,0 +1,13 @@ +namespace Vault.Models +{ +#pragma warning disable SA1602 // Enumeration items should be documented + public enum DatabaseType + { + Unknown, + + SQLite, + + SqlServer + } +#pragma warning restore SA1602 // Enumeration items should be documented +} diff --git a/Vault/Models/LoginResult.cs b/Vault/Models/LoginResult.cs index 0538d6c0..939a750a 100644 --- a/Vault/Models/LoginResult.cs +++ b/Vault/Models/LoginResult.cs @@ -6,7 +6,7 @@ public LoginResult(string userID) => UserID = userID; public static LoginResult Failed => - new (null); + new(null); public string UserID { get; } diff --git a/Vault/Startup.cs b/Vault/Startup.cs index e76c9473..06100074 100644 --- a/Vault/Startup.cs +++ b/Vault/Startup.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Newtonsoft.Json.Serialization; +using Vault.Models; using Vault.Support; namespace Vault @@ -32,9 +33,11 @@ public void ConfigureServices(IServiceCollection services) services.AddMvc(options => options.Filters.Add()); + SqlStatements.DatabaseType = Configuration["DbType"] == "SQLite" ? DatabaseType.SQLite : DatabaseType.SqlServer; + var connectionString = Configuration.GetConnectionString("Main"); - var connectionFactory = Configuration["DbType"] == "SQLite" + var connectionFactory = SqlStatements.DatabaseType == DatabaseType.SQLite ? new Func(_ => new SQLiteConnectionFactory(connectionString)) : _ => new SqlConnectionFactory(connectionString); diff --git a/Vault/Support/InlineJsGlobalSerializer.cs b/Vault/Support/InlineJsGlobalSerializer.cs index 93ef5edf..105e819b 100644 --- a/Vault/Support/InlineJsGlobalSerializer.cs +++ b/Vault/Support/InlineJsGlobalSerializer.cs @@ -10,7 +10,7 @@ namespace Vault.Support public static class InlineJsGlobalSerializer { private static readonly JsonSerializerSettings _settings = - new () { + new() { NullValueHandling = NullValueHandling.Ignore, ContractResolver = new DefaultContractResolver { NamingStrategy = new CamelCaseNamingStrategy() @@ -40,7 +40,7 @@ public static string AsJson(this object o) } private static JsonTextWriter GetJsonWriter(StringWriter sw) => - new (sw) { + new(sw) { Formatting = Formatting.Indented, IndentChar = ' ', Indentation = 4, diff --git a/Vault/Support/SqlStatements.cs b/Vault/Support/SqlStatements.cs index e8973816..c56503bb 100644 --- a/Vault/Support/SqlStatements.cs +++ b/Vault/Support/SqlStatements.cs @@ -1,11 +1,13 @@ -namespace Vault.Support +using System; +using Vault.Models; + +namespace Vault.Support { public static class SqlStatements { public const string SelectSummary = @"SELECT CredentialID, - UserID, Description, Username, Password, @@ -19,38 +21,60 @@ public static class SqlStatements "SELECT * FROM Credentials WHERE UserID = @UserID"; public const string SelectSingle = - "SELECT * FROM Credentials WHERE CredentialID = @CredentialID"; + """ + SELECT + * + FROM + Credentials + WHERE + CredentialID = @CredentialID; + + SELECT + tc.TagID, + t.Label + FROM + Tags_Credentials tc + INNER JOIN + Tags t + ON t.TagID = tc.TagID + WHERE + tc.CredentialID = @CredentialID + ORDER BY + t.Label; + """; public const string Insert = - @"INSERT INTO - Credentials ( - CredentialID, - UserID, - Description, - Username, - Password, - Url, - UserDefined1Label, - UserDefined1, - UserDefined2Label, - UserDefined2, - Notes, - PwdOptions - ) - VALUES ( - @CredentialID, - @UserID, - @Description, - @Username, - @Password, - @Url, - @UserDefined1Label, - @UserDefined1, - @UserDefined2Label, - @UserDefined2, - @Notes, - @PwdOptions - )"; + """ + INSERT INTO + Credentials ( + CredentialID, + UserID, + Description, + Username, + Password, + Url, + UserDefined1Label, + UserDefined1, + UserDefined2Label, + UserDefined2, + Notes, + PwdOptions + ) + VALUES ( + @CredentialID, + @UserID, + @Description, + @Username, + @Password, + @Url, + @UserDefined1Label, + @UserDefined1, + @UserDefined2Label, + @UserDefined2, + @Notes, + @PwdOptions + ); + """; public const string Update = @"UPDATE @@ -77,5 +101,54 @@ public static class SqlStatements public const string Login = "SELECT UserID FROM Users WHERE Username = @Username AND Password = @Password"; + + public const string TagIndex = + """ + SELECT + TagID, + Label + FROM + Tags + WHERE + UserID = @UserID + ORDER BY + Label; + + SELECT + tc.TagID, + tc.CredentialID + FROM + Tags_Credentials tc + INNER JOIN + Tags t + ON t.TagID = tc.TagID + WHERE + t.UserID = @UserID + ORDER BY + t.Label; + """; + + public const string DeleteTagsFromCredential = + """ + DELETE FROM + Tags_Credentials + WHERE + CredentialID = @CredentialID; + """; + + public const string TagsToCredential = + """ + INSERT INTO + Tags_Credentials ( + TagID, + CredentialID + ) + VALUES ( + @TagID, + @CredentialID + ); + """; + + public static DatabaseType DatabaseType { get; internal set; } } } diff --git a/Vault/Vault.csproj b/Vault/Vault.csproj index e61a545a..68f9c9cf 100644 --- a/Vault/Vault.csproj +++ b/Vault/Vault.csproj @@ -21,16 +21,16 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers diff --git a/Vault/Views/Home/GenerateVaultCredential.cshtml b/Vault/Views/Home/GenerateVaultCredential.cshtml index 50216fc6..82668dbb 100644 --- a/Vault/Views/Home/GenerateVaultCredential.cshtml +++ b/Vault/Views/Home/GenerateVaultCredential.cshtml @@ -59,7 +59,7 @@ @section foot { - + diff --git a/Vault/Views/Home/Index.cshtml b/Vault/Views/Home/Index.cshtml index 2fc68b44..b561bdff 100644 --- a/Vault/Views/Home/Index.cshtml +++ b/Vault/Views/Home/Index.cshtml @@ -20,6 +20,6 @@ var _VAULT_GLOBALS = @Html.Raw(Model.AsJson()); - + } diff --git a/Vault/Views/Shared/_ClientTemplates.cshtml b/Vault/Views/Shared/_ClientTemplates.cshtml index f2fce0fb..dd77cb58 100644 --- a/Vault/Views/Shared/_ClientTemplates.cshtml +++ b/Vault/Views/Shared/_ClientTemplates.cshtml @@ -88,6 +88,17 @@
{{breaklines Notes}}
{{/if}} + + {{#if TagDisplay}} +
+
Tags
+
+ {{#each TagDisplay}} +
{{Label}}
+ {{/each}} +
+
+ {{/if}}