From 2233d6c3be1b2417b6d88869b42d0f77f64b40be Mon Sep 17 00:00:00 2001 From: Uncled1023 Date: Mon, 6 Dec 2021 00:13:12 -0800 Subject: [PATCH] Added Auth Tokens for direct API access --- IdentityServer/App_Data/version.json | 4 + IdentityServer/App_Data/version.template.json | 4 + IdentityServer/ApplicationDbContext.cs | 29 ++ .../Controllers/ManageController.cs | 153 ++++++++ .../20211206044736_AuthToken.Designer.cs | 328 ++++++++++++++++++ .../ApplicationDb/20211206044736_AuthToken.cs | 42 +++ .../ApplicationDbContextModelSnapshot.cs | 59 +++- IdentityServer/IdentityServer.csproj | 1 - IdentityServer/Models/ApplicationUser.cs | 4 + IdentityServer/Models/AuthToken.cs | 24 ++ .../Models/Manage/CreateAuthTokenModel.cs | 13 + .../Models/Manage/DeleteAuthTokenModel.cs | 12 + .../Models/Manage/EditAuthTokenModel.cs | 10 + Teknik/App_Data/endpointMappings.json | 17 +- .../V1/Controllers/PasteAPIv1Controller.cs | 1 - .../Areas/User/Controllers/UserController.cs | 265 ++++++-------- Teknik/Areas/User/Models/AuthToken.cs | 16 + Teknik/Areas/User/Models/IdentityUserInfo.cs | 10 + Teknik/Areas/User/Utility/IdentityHelper.cs | 87 +++++ .../ViewModels/AuthTokenSettingsViewModel.cs | 14 + .../User/ViewModels/AuthTokenViewModel.cs | 2 +- ...iewModel.cs => ClientSettingsViewModel.cs} | 7 +- .../User/Settings/AuthTokenSettings.cshtml | 76 ++++ ...{AuthToken.cshtml => AuthTokenView.cshtml} | 4 +- ...rSettings.cshtml => ClientSettings.cshtml} | 6 +- .../User/Views/User/Settings/Settings.cshtml | 6 +- .../Settings/{Developer.css => Client.css} | 0 Teknik/Scripts/User/AuthTokenSettings.js | 196 +++++++++++ ...DeveloperSettings.js => ClientSettings.js} | 0 .../AuthTokenAuthenticationHandler.cs | 97 ++++++ Teknik/Security/AuthTokenSchemeOptions.cs | 13 + Teknik/Startup.cs | 11 +- Teknik/bundleconfig.json | 14 +- Utilities/Attributes/EntityAttribute.cs | 17 + Utilities/Constants.cs | 2 + 35 files changed, 1351 insertions(+), 193 deletions(-) create mode 100644 IdentityServer/App_Data/version.json create mode 100644 IdentityServer/App_Data/version.template.json create mode 100644 IdentityServer/Data/Migrations/ApplicationDb/20211206044736_AuthToken.Designer.cs create mode 100644 IdentityServer/Data/Migrations/ApplicationDb/20211206044736_AuthToken.cs create mode 100644 IdentityServer/Models/AuthToken.cs create mode 100644 IdentityServer/Models/Manage/CreateAuthTokenModel.cs create mode 100644 IdentityServer/Models/Manage/DeleteAuthTokenModel.cs create mode 100644 IdentityServer/Models/Manage/EditAuthTokenModel.cs create mode 100644 Teknik/Areas/User/Models/AuthToken.cs create mode 100644 Teknik/Areas/User/ViewModels/AuthTokenSettingsViewModel.cs rename Teknik/Areas/User/ViewModels/{DeveloperSettingsViewModel.cs => ClientSettingsViewModel.cs} (55%) create mode 100644 Teknik/Areas/User/Views/User/Settings/AuthTokenSettings.cshtml rename Teknik/Areas/User/Views/User/Settings/{AuthToken.cshtml => AuthTokenView.cshtml} (85%) rename Teknik/Areas/User/Views/User/Settings/{DeveloperSettings.cshtml => ClientSettings.cshtml} (96%) rename Teknik/Content/User/Settings/{Developer.css => Client.css} (100%) create mode 100644 Teknik/Scripts/User/AuthTokenSettings.js rename Teknik/Scripts/User/{DeveloperSettings.js => ClientSettings.js} (100%) create mode 100644 Teknik/Security/AuthTokenAuthenticationHandler.cs create mode 100644 Teknik/Security/AuthTokenSchemeOptions.cs create mode 100644 Utilities/Attributes/EntityAttribute.cs diff --git a/IdentityServer/App_Data/version.json b/IdentityServer/App_Data/version.json new file mode 100644 index 0000000..1cb5524 --- /dev/null +++ b/IdentityServer/App_Data/version.json @@ -0,0 +1,4 @@ +{ + "version": "5.1.0", + "hash": "37fc3ca56005b7eca9143ae4c74f4163e9417856" +} \ No newline at end of file diff --git a/IdentityServer/App_Data/version.template.json b/IdentityServer/App_Data/version.template.json new file mode 100644 index 0000000..daa8908 --- /dev/null +++ b/IdentityServer/App_Data/version.template.json @@ -0,0 +1,4 @@ +{ + "version": "{{git_ver}}", + "hash": "{{git_hash}}" +} \ No newline at end of file diff --git a/IdentityServer/ApplicationDbContext.cs b/IdentityServer/ApplicationDbContext.cs index 4751fa6..9053c84 100644 --- a/IdentityServer/ApplicationDbContext.cs +++ b/IdentityServer/ApplicationDbContext.cs @@ -1,11 +1,40 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; +using System.Linq; using Teknik.IdentityServer.Models; +using Teknik.Utilities.Attributes; namespace Teknik.IdentityServer { public class ApplicationDbContext : IdentityDbContext { + public DbSet AuthTokens { get; set; } + public ApplicationDbContext(DbContextOptions options) : base(options) { } + + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + // User + modelBuilder.Entity().HasMany(u => u.AuthTokens).WithOne(t => t.ApplicationUser).HasForeignKey(t => t.ApplicationUserId); + + // Auth Tokens + modelBuilder.Entity().ToTable("AuthTokens"); + + // Custom Attributes + foreach (var entityType in modelBuilder.Model.GetEntityTypes()) + { + foreach (var property in entityType.GetProperties()) + { + var attributes = property?.PropertyInfo?.GetCustomAttributes(typeof(CaseSensitiveAttribute), false); + if (attributes != null && attributes.Any()) + { + property.SetAnnotation("CaseSensitive", true); + } + } + } + } } } \ No newline at end of file diff --git a/IdentityServer/Controllers/ManageController.cs b/IdentityServer/Controllers/ManageController.cs index 4331949..eb82f84 100644 --- a/IdentityServer/Controllers/ManageController.cs +++ b/IdentityServer/Controllers/ManageController.cs @@ -139,6 +139,36 @@ namespace Teknik.IdentityServer.Controllers return new JsonResult(new { success = false, message = "User does not exist." }); } + [HttpGet] + public IActionResult GetUserInfoByAuthToken(string authToken) + { + if (string.IsNullOrEmpty(authToken)) + return new JsonResult(new { success = false, message = "Auth Token Required" }); + + var foundUser = GetCachedUserByAuthToken(authToken); + if (foundUser != null) + { + var userJson = foundUser.ToJson(); + return new JsonResult(new { success = true, data = userJson }); + } + return new JsonResult(new { success = false, message = "User does not exist." }); + } + + [HttpGet] + public async Task GetUserClaims(string username, [FromServices] IUserClaimsPrincipalFactory claimsFactory) + { + if (string.IsNullOrEmpty(username)) + return new JsonResult(new { success = false, message = "Username is required" }); + + var foundUser = await GetCachedUser(username); + if (foundUser != null) + { + var principal = await claimsFactory.CreateAsync(foundUser); + return new JsonResult(new { success = true, data = principal.Claims.ToList() }); + } + return new JsonResult(new { success = false, message = "User does not exist." }); + } + [HttpPost] public async Task CheckPassword(CheckPasswordModel model) { @@ -672,6 +702,109 @@ namespace Teknik.IdentityServer.Controllers return new JsonResult(new { success = false, message = "Client does not exist." }); } + [HttpGet] + public IActionResult GetAuthToken(string authTokenId, [FromServices] ApplicationDbContext dbContext) + { + var authToken = dbContext.AuthTokens.FirstOrDefault(t => t.AuthTokenId == Guid.Parse(authTokenId)); + if (authToken != null) + { + return new JsonResult(new { success = true, data = new { authTokenId = authToken.AuthTokenId, name = authToken.Name, token = authToken.Token } }); + } + + return new JsonResult(new { success = false, message = "Auth Token does not exist." }); + } + + [HttpGet] + public async Task GetAuthTokens(string username) + { + if (string.IsNullOrEmpty(username)) + return new JsonResult(new { success = false, message = "Username is required" }); + + var foundUser = await GetCachedUser(username); + if (foundUser != null) + { + var authTokens = foundUser.AuthTokens.Select(t => new { authTokenId = t.AuthTokenId, name = t.Name, token = t.Token }).ToList(); + return new JsonResult(new { success = true, data = authTokens }); + } + return new JsonResult(new { success = false, message = "User does not exist" }); + } + + [HttpPost] + public IActionResult CreateAuthToken(CreateAuthTokenModel model, [FromServices] ApplicationDbContext dbContext) + { + + if (string.IsNullOrEmpty(model.Username)) + return new JsonResult(new { success = false, message = "Username is required" }); + + var foundUser = dbContext.Users.FirstOrDefault(u => u.UserName == model.Username); + if (foundUser != null) + { + // Generate a unique token + var token = StringHelper.RandomString(40, "abcdefghjkmnpqrstuvwxyz1234567890"); + var authToken = new AuthToken() + { + AuthTokenId = Guid.NewGuid(), + ApplicationUser = foundUser, + Name = model.Name, + Token = token + }; + + dbContext.AuthTokens.Add(authToken); + dbContext.SaveChanges(); + + // Clear the user cache + RemoveCachedUser(model.Username); + + return new JsonResult(new { success = true, data = new { authTokenId = authToken.AuthTokenId, token = token } }); + } + return new JsonResult(new { success = false, message = "User does not exist" }); + } + + [HttpPost] + public IActionResult EditAuthToken(EditAuthTokenModel model, [FromServices] ApplicationDbContext applicationDb) + { + var foundAuthToken = applicationDb.AuthTokens.FirstOrDefault(t => t.AuthTokenId == Guid.Parse(model.AuthTokenId)); + if (foundAuthToken != null) + { + foundAuthToken.Name = model.Name; + applicationDb.Entry(foundAuthToken).State = EntityState.Modified; + applicationDb.SaveChanges(); + + // Clear the user cache + RemoveCachedUser(foundAuthToken.ApplicationUser.UserName); + + // Clear the user cache by token + RemoveCachedUser(foundAuthToken.Token); + + return new JsonResult(new { success = true }); + } + + return new JsonResult(new { success = false, message = "Auth Token does not exist." }); + } + + [HttpPost] + public IActionResult DeleteAuthToken(DeleteAuthTokenModel model, [FromServices] ApplicationDbContext applicationDb) + { + var foundAuthToken = applicationDb.AuthTokens.FirstOrDefault(t => t.AuthTokenId == Guid.Parse(model.AuthTokenId)); + if (foundAuthToken != null) + { + var username = foundAuthToken.ApplicationUser.UserName; + var token = foundAuthToken.Token; + applicationDb.AuthTokens.Remove(foundAuthToken); + applicationDb.SaveChanges(); + + // Clear the user cache + RemoveCachedUser(username); + + // Clear the user cache by token + RemoveCachedUser(token); + + return new JsonResult(new { success = true }); + } + + return new JsonResult(new { success = false, message = "Auth Token does not exist." }); + } + private string FormatKey(string unformattedKey) { var result = new StringBuilder(); @@ -709,6 +842,26 @@ namespace Teknik.IdentityServer.Controllers return foundUser; } + private ApplicationUser GetCachedUserByAuthToken(string token) + { + if (string.IsNullOrEmpty(token)) + throw new ArgumentNullException("token"); + + // Check the cache + string cacheKey = GetKey(token); + ApplicationUser foundUser; + if (!_cache.TryGetValue(cacheKey, out foundUser)) + { + foundUser = _userManager.Users.FirstOrDefault(u => u.AuthTokens.FirstOrDefault(t => t.Token == token) != null); + if (foundUser != null) + { + _cache.AddToCache(cacheKey, foundUser, new TimeSpan(1, 0, 0)); + } + } + + return foundUser; + } + private void RemoveCachedUser(string username) { if (string.IsNullOrEmpty(username)) diff --git a/IdentityServer/Data/Migrations/ApplicationDb/20211206044736_AuthToken.Designer.cs b/IdentityServer/Data/Migrations/ApplicationDb/20211206044736_AuthToken.Designer.cs new file mode 100644 index 0000000..9e27d42 --- /dev/null +++ b/IdentityServer/Data/Migrations/ApplicationDb/20211206044736_AuthToken.Designer.cs @@ -0,0 +1,328 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Teknik.IdentityServer; + +namespace Teknik.IdentityServer.Data.Migrations.ApplicationDb +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20211206044736_AuthToken")] + partial class AuthToken + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.7") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Teknik.IdentityServer.Models.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("AccountStatus") + .HasColumnType("int"); + + b.Property("AccountType") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LastEdit") + .HasColumnType("datetime2"); + + b.Property("LastSeen") + .HasColumnType("datetime2"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PGPPublicKey") + .HasColumnType("nvarchar(max)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Teknik.IdentityServer.Models.AuthToken", b => + { + b.Property("AuthTokenId") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApplicationUserId") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Token") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.HasKey("AuthTokenId"); + + b.HasIndex("ApplicationUserId"); + + b.ToTable("AuthTokens"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Teknik.IdentityServer.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Teknik.IdentityServer.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Teknik.IdentityServer.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Teknik.IdentityServer.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Teknik.IdentityServer.Models.AuthToken", b => + { + b.HasOne("Teknik.IdentityServer.Models.ApplicationUser", "ApplicationUser") + .WithMany("AuthTokens") + .HasForeignKey("ApplicationUserId"); + + b.Navigation("ApplicationUser"); + }); + + modelBuilder.Entity("Teknik.IdentityServer.Models.ApplicationUser", b => + { + b.Navigation("AuthTokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/IdentityServer/Data/Migrations/ApplicationDb/20211206044736_AuthToken.cs b/IdentityServer/Data/Migrations/ApplicationDb/20211206044736_AuthToken.cs new file mode 100644 index 0000000..f669e75 --- /dev/null +++ b/IdentityServer/Data/Migrations/ApplicationDb/20211206044736_AuthToken.cs @@ -0,0 +1,42 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Teknik.IdentityServer.Data.Migrations.ApplicationDb +{ + public partial class AuthToken : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AuthTokens", + columns: table => new + { + AuthTokenId = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(max)", nullable: true), + ApplicationUserId = table.Column(type: "nvarchar(450)", nullable: true), + Token = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AuthTokens", x => x.AuthTokenId); + table.ForeignKey( + name: "FK_AuthTokens_AspNetUsers_ApplicationUserId", + column: x => x.ApplicationUserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_AuthTokens_ApplicationUserId", + table: "AuthTokens", + column: "ApplicationUserId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AuthTokens"); + } + } +} diff --git a/IdentityServer/Data/Migrations/ApplicationDb/ApplicationDbContextModelSnapshot.cs b/IdentityServer/Data/Migrations/ApplicationDb/ApplicationDbContextModelSnapshot.cs index 5676d30..0d34133 100644 --- a/IdentityServer/Data/Migrations/ApplicationDb/ApplicationDbContextModelSnapshot.cs +++ b/IdentityServer/Data/Migrations/ApplicationDb/ApplicationDbContextModelSnapshot.cs @@ -16,13 +16,12 @@ namespace Teknik.IdentityServer.Data.Migrations.ApplicationDb #pragma warning disable 612, 618 modelBuilder .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "2.1.4-rtm-31024") + .HasAnnotation("ProductVersion", "5.0.7") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => { b.Property("Id") - .ValueGeneratedOnAdd() .HasColumnType("nvarchar(450)"); b.Property("ConcurrencyStamp") @@ -154,7 +153,6 @@ namespace Teknik.IdentityServer.Data.Migrations.ApplicationDb modelBuilder.Entity("Teknik.IdentityServer.Models.ApplicationUser", b => { b.Property("Id") - .ValueGeneratedOnAdd() .HasColumnType("nvarchar(450)"); b.Property("AccessFailedCount") @@ -235,12 +233,36 @@ namespace Teknik.IdentityServer.Data.Migrations.ApplicationDb b.ToTable("AspNetUsers"); }); + modelBuilder.Entity("Teknik.IdentityServer.Models.AuthToken", b => + { + b.Property("AuthTokenId") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApplicationUserId") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Token") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.HasKey("AuthTokenId"); + + b.HasIndex("ApplicationUserId"); + + b.ToTable("AuthTokens"); + }); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => { b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) .WithMany() .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => @@ -248,7 +270,8 @@ namespace Teknik.IdentityServer.Data.Migrations.ApplicationDb b.HasOne("Teknik.IdentityServer.Models.ApplicationUser", null) .WithMany() .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => @@ -256,7 +279,8 @@ namespace Teknik.IdentityServer.Data.Migrations.ApplicationDb b.HasOne("Teknik.IdentityServer.Models.ApplicationUser", null) .WithMany() .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => @@ -264,12 +288,14 @@ namespace Teknik.IdentityServer.Data.Migrations.ApplicationDb b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) .WithMany() .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.HasOne("Teknik.IdentityServer.Models.ApplicationUser", null) .WithMany() .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => @@ -277,7 +303,22 @@ namespace Teknik.IdentityServer.Data.Migrations.ApplicationDb b.HasOne("Teknik.IdentityServer.Models.ApplicationUser", null) .WithMany() .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Teknik.IdentityServer.Models.AuthToken", b => + { + b.HasOne("Teknik.IdentityServer.Models.ApplicationUser", "ApplicationUser") + .WithMany("AuthTokens") + .HasForeignKey("ApplicationUserId"); + + b.Navigation("ApplicationUser"); + }); + + modelBuilder.Entity("Teknik.IdentityServer.Models.ApplicationUser", b => + { + b.Navigation("AuthTokens"); }); #pragma warning restore 612, 618 } diff --git a/IdentityServer/IdentityServer.csproj b/IdentityServer/IdentityServer.csproj index 770f984..561b629 100644 --- a/IdentityServer/IdentityServer.csproj +++ b/IdentityServer/IdentityServer.csproj @@ -15,7 +15,6 @@ - diff --git a/IdentityServer/Models/ApplicationUser.cs b/IdentityServer/Models/ApplicationUser.cs index 8e040b3..8acf016 100644 --- a/IdentityServer/Models/ApplicationUser.cs +++ b/IdentityServer/Models/ApplicationUser.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Identity; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; @@ -23,6 +24,8 @@ namespace Teknik.IdentityServer.Models public DateTime LastEdit { get; set; } + public virtual ICollection AuthTokens { get; set; } + public ApplicationUser() : base() { Init(); @@ -41,6 +44,7 @@ namespace Teknik.IdentityServer.Models AccountType = AccountType.Basic; AccountStatus = AccountStatus.Active; PGPPublicKey = null; + AuthTokens = new List(); } public List ToClaims() diff --git a/IdentityServer/Models/AuthToken.cs b/IdentityServer/Models/AuthToken.cs new file mode 100644 index 0000000..09e3ee9 --- /dev/null +++ b/IdentityServer/Models/AuthToken.cs @@ -0,0 +1,24 @@ +using System; +using System.Text.Json.Serialization; +using Teknik.Utilities.Attributes; + +namespace Teknik.IdentityServer.Models +{ + public class AuthToken + { + public Guid AuthTokenId { get; set; } + + public string Name { get; set; } + + [JsonIgnore] + public string ApplicationUserId { get; set; } + + + [JsonIgnore] + public virtual ApplicationUser ApplicationUser { get; set; } + + [JsonIgnore] + [CaseSensitive] + public string Token { get; set; } + } +} diff --git a/IdentityServer/Models/Manage/CreateAuthTokenModel.cs b/IdentityServer/Models/Manage/CreateAuthTokenModel.cs new file mode 100644 index 0000000..027d7da --- /dev/null +++ b/IdentityServer/Models/Manage/CreateAuthTokenModel.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Teknik.IdentityServer.Models.Manage +{ + public class CreateAuthTokenModel + { + public string Username { get; set; } + public string Name { get; set; } + } +} diff --git a/IdentityServer/Models/Manage/DeleteAuthTokenModel.cs b/IdentityServer/Models/Manage/DeleteAuthTokenModel.cs new file mode 100644 index 0000000..781308d --- /dev/null +++ b/IdentityServer/Models/Manage/DeleteAuthTokenModel.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Teknik.IdentityServer.Models.Manage +{ + public class DeleteAuthTokenModel + { + public string AuthTokenId { get; set; } + } +} diff --git a/IdentityServer/Models/Manage/EditAuthTokenModel.cs b/IdentityServer/Models/Manage/EditAuthTokenModel.cs new file mode 100644 index 0000000..8d8a796 --- /dev/null +++ b/IdentityServer/Models/Manage/EditAuthTokenModel.cs @@ -0,0 +1,10 @@ +using System; + +namespace Teknik.IdentityServer.Models.Manage +{ + public class EditAuthTokenModel + { + public string AuthTokenId { get; set; } + public string Name { get; set; } + } +} diff --git a/Teknik/App_Data/endpointMappings.json b/Teknik/App_Data/endpointMappings.json index 6cd5797..f8a0377 100644 --- a/Teknik/App_Data/endpointMappings.json +++ b/Teknik/App_Data/endpointMappings.json @@ -1090,14 +1090,25 @@ } }, { - "Name": "User.DeveloperSettings", + "Name": "User.ClientSettings", "HostTypes": [ "Full" ], "SubDomains": [ "account" ], - "Pattern": "Settings/Developer", + "Pattern": "Settings/Clients", "Area": "User", "Defaults": { "controller": "User", - "action": "DeveloperSettings" + "action": "ClientSettings" + } + }, + { + "Name": "User.AuthTokenSettings", + "HostTypes": [ "Full" ], + "SubDomains": [ "account" ], + "Pattern": "Settings/AuthTokens", + "Area": "User", + "Defaults": { + "controller": "User", + "action": "AuthTokenSettings" } }, { diff --git a/Teknik/Areas/API/V1/Controllers/PasteAPIv1Controller.cs b/Teknik/Areas/API/V1/Controllers/PasteAPIv1Controller.cs index 55f1f52..235e61a 100644 --- a/Teknik/Areas/API/V1/Controllers/PasteAPIv1Controller.cs +++ b/Teknik/Areas/API/V1/Controllers/PasteAPIv1Controller.cs @@ -25,7 +25,6 @@ namespace Teknik.Areas.API.V1.Controllers public PasteAPIv1Controller(ILogger logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { } [HttpPost] - [AllowAnonymous] [TrackPageView] public IActionResult Paste(PasteAPIv1Model model) { diff --git a/Teknik/Areas/User/Controllers/UserController.cs b/Teknik/Areas/User/Controllers/UserController.cs index 35ca097..0fbcc89 100644 --- a/Teknik/Areas/User/Controllers/UserController.cs +++ b/Teknik/Areas/User/Controllers/UserController.cs @@ -418,17 +418,6 @@ namespace Teknik.Areas.Users.Controllers // Get the user secure info IdentityUserInfo userInfo = await IdentityHelper.GetIdentityUserInfo(_config, user.Username); - //model.TrustedDeviceCount = user.TrustedDevices.Count; - //model.AuthTokens = new List(); - //foreach (AuthToken token in user.AuthTokens) - //{ - // AuthTokenViewModel tokenModel = new AuthTokenViewModel(); - // tokenModel.AuthTokenId = token.AuthTokenId; - // tokenModel.Name = token.Name; - // tokenModel.LastDateUsed = token.LastDateUsed; - - // model.AuthTokens.Add(tokenModel); - //} model.PgpPublicKey = userInfo.PGPPublicKey; model.RecoveryEmail = userInfo.RecoveryEmail; @@ -444,32 +433,22 @@ namespace Teknik.Areas.Users.Controllers } [TrackPageView] - public async Task DeveloperSettings() + public async Task ClientSettings() { string username = User.Identity.Name; User user = UserHelper.GetUser(_dbContext, username); if (user != null) { - ViewBag.Title = "Developer Settings"; - ViewBag.Description = "Your " + _config.Title + " Developer Settings"; + ViewBag.Title = "Client Settings"; + ViewBag.Description = "Your " + _config.Title + " Client Settings"; - DeveloperSettingsViewModel model = new DeveloperSettingsViewModel(); - model.Page = "Developer"; + ClientSettingsViewModel model = new ClientSettingsViewModel(); + model.Page = "Clients"; model.UserID = user.UserId; model.Username = user.Username; - model.AuthTokens = new List(); model.Clients = new List(); - //foreach (AuthToken token in user.AuthTokens) - //{ - // AuthTokenViewModel tokenModel = new AuthTokenViewModel(); - // tokenModel.AuthTokenId = token.AuthTokenId; - // tokenModel.Name = token.Name; - // tokenModel.LastDateUsed = token.LastDateUsed; - - // model.AuthTokens.Add(tokenModel); - //} Client[] clients = await IdentityHelper.GetClients(_config, username); foreach (Client client in clients) @@ -486,7 +465,42 @@ namespace Teknik.Areas.Users.Controllers }); } - return View("/Areas/User/Views/User/Settings/DeveloperSettings.cshtml", model); + return View("/Areas/User/Views/User/Settings/ClientSettings.cshtml", model); + } + + return new StatusCodeResult(StatusCodes.Status403Forbidden); + } + + [TrackPageView] + public async Task AuthTokenSettings() + { + string username = User.Identity.Name; + User user = UserHelper.GetUser(_dbContext, username); + + if (user != null) + { + ViewBag.Title = "Auth Tokens"; + ViewBag.Description = "Your " + _config.Title + " - Developer Settings"; + + AuthTokenSettingsViewModel model = new AuthTokenSettingsViewModel(); + model.Page = "AuthTokens"; + model.UserID = user.UserId; + model.Username = user.Username; + + model.AuthTokens = new List(); + + AuthToken[] authTokens = await IdentityHelper.GetAuthTokens(_config, username); + foreach (AuthToken token in authTokens) + { + AuthTokenViewModel tokenModel = new AuthTokenViewModel(); + tokenModel.AuthTokenId = token.AuthTokenId; + tokenModel.Name = token.Name; + tokenModel.LastDateUsed = token.LastUsed; + + model.AuthTokens.Add(tokenModel); + } + + return View("/Areas/User/Views/User/Settings/AuthTokenSettings.cshtml", model); } return new StatusCodeResult(StatusCodes.Status403Forbidden); @@ -704,20 +718,6 @@ namespace Teknik.Areas.Users.Controllers } } - //if (!settings.TwoFactorEnabled && (!userInfo.TwoFactorEnabled.HasValue || userInfo.TwoFactorEnabled.Value)) - //{ - // var result = await IdentityHelper.Disable2FA(_config, user.Username); - // if (!result.Success) - // return Json(new { error = result.Message }); - //} - - //UserHelper.EditAccount(_dbContext, _config, user, changePass, settings.NewPassword); - - - //if (!oldTwoFactor && settings.TwoFactorEnabled) - //{ - // return Json(new { result = new { checkAuth = true, key = newKey, qrUrl = Url.SubRouteUrl("account", "User.Action", new { action = "GenerateAuthQrCode", key = newKey }) } }); - //} return Json(new { result = true }); } return Json(new { error = "User does not exist" }); @@ -1124,33 +1124,32 @@ namespace Teknik.Areas.Users.Controllers [HttpPost] [ValidateAntiForgeryToken] - public IActionResult ClearTrustedDevices() + public async Task CreateAuthToken(string name, [FromServices] ICompositeViewEngine viewEngine) { try { - User user = UserHelper.GetUser(_dbContext, User.Identity.Name); - if (user != null) - { - //if (user.SecuritySettings.AllowTrustedDevices) - //{ - // // let's clear the trusted devices - // user.TrustedDevices.Clear(); - // List foundDevices = _dbContext.TrustedDevices.Where(d => d.UserId == user.UserId).ToList(); - // if (foundDevices != null) - // { - // foreach (TrustedDevice device in foundDevices) - // { - // _dbContext.TrustedDevices.Remove(device); - // } - // } - // _dbContext.Entry(user).State = EntityState.Modified; - // _dbContext.SaveChanges(); + if (string.IsNullOrEmpty(name)) + return Json(new { error = "You must enter an auth token name" }); - // return Json(new { result = true }); - //} - return Json(new { error = "User does not allow trusted devices" }); + // Validate the code with the identity server + var result = await IdentityHelper.CreateAuthToken( + _config, + User.Identity.Name, + name); + + if (result.Success) + { + var authToken = (JObject)result.Data; + + AuthTokenViewModel model = new AuthTokenViewModel(); + model.AuthTokenId = authToken["authTokenId"].ToString(); + model.Name = name; + + string renderedView = await RenderPartialViewToString(viewEngine, "~/Areas/User/Views/User/Settings/AuthTokenView.cshtml", model); + + return Json(new { result = true, authTokenId = model.AuthTokenId, token = authToken["token"].ToString(), html = renderedView }); } - return Json(new { error = "User does not exist" }); + return Json(new { error = result.Message }); } catch (Exception ex) { @@ -1160,37 +1159,55 @@ namespace Teknik.Areas.Users.Controllers [HttpPost] [ValidateAntiForgeryToken] - public async Task GenerateToken(string name, [FromServices] ICompositeViewEngine viewEngine) + public async Task GetAuthToken(string authTokenId) + { + AuthToken foundAuthToken = await IdentityHelper.GetAuthToken(_config, authTokenId); + if (foundAuthToken != null) + { + AuthTokenViewModel model = new AuthTokenViewModel() + { + AuthTokenId = foundAuthToken.AuthTokenId.ToString(), + Name = foundAuthToken.Name + }; + + return Json(new { result = true, authToken = model }); + } + return new StatusCodeResult(StatusCodes.Status403Forbidden); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task EditAuthToken(string authTokenId, string name, [FromServices] ICompositeViewEngine viewEngine) { try { - User user = UserHelper.GetUser(_dbContext, User.Identity.Name); - if (user != null) + if (string.IsNullOrEmpty(name)) + return Json(new { error = "You must enter an auth token name" }); + + AuthToken foundAuthToken = await IdentityHelper.GetAuthToken(_config, authTokenId); + + if (foundAuthToken == null) + return Json(new { error = "Auth Token does not exist" }); + + // Validate the code with the identity server + var result = await IdentityHelper.EditAuthToken( + _config, + authTokenId, + name); + + if (result.Success) { - //string newTokenStr = UserHelper.GenerateAuthToken(_dbContext, user.Username); + var authToken = (JObject)result.Data; - //if (!string.IsNullOrEmpty(newTokenStr)) - //{ - // AuthToken token = new AuthToken(); - // token.UserId = user.UserId; - // token.HashedToken = SHA256.Hash(newTokenStr); - // token.Name = name; + AuthTokenViewModel model = new AuthTokenViewModel(); + model.AuthTokenId = authTokenId; + model.Name = name; - // _dbContext.AuthTokens.Add(token); - // _dbContext.SaveChanges(); + string renderedView = await RenderPartialViewToString(viewEngine, "~/Areas/User/Views/User/Settings/AuthTokenView.cshtml", model); - // AuthTokenViewModel model = new AuthTokenViewModel(); - // model.AuthTokenId = token.AuthTokenId; - // model.Name = token.Name; - // model.LastDateUsed = token.LastDateUsed; - - // string renderedView = await RenderPartialViewToString(viewEngine, "~/Areas/User/Views/User/Settings/AuthToken.cshtml", model); - - // return Json(new { result = new { token = newTokenStr, html = renderedView } }); - //} - return Json(new { error = "Unable to generate Auth Token" }); + return Json(new { result = true, authTokenId = authTokenId, html = renderedView }); } - return Json(new { error = "User does not exist" }); + return Json(new { error = result.Message }); } catch (Exception ex) { @@ -1200,85 +1217,19 @@ namespace Teknik.Areas.Users.Controllers [HttpPost] [ValidateAntiForgeryToken] - public IActionResult RevokeAllTokens() + public async Task DeleteAuthToken(string authTokenId) { try { - User user = UserHelper.GetUser(_dbContext, User.Identity.Name); - if (user != null) + var result = await IdentityHelper.DeleteAuthToken(_config, authTokenId); + if (result.Success) { - //user.AuthTokens.Clear(); - //List foundTokens = _dbContext.AuthTokens.Where(d => d.UserId == user.UserId).ToList(); - //if (foundTokens != null) - //{ - // foreach (AuthToken token in foundTokens) - // { - // _dbContext.AuthTokens.Remove(token); - // } - //} - _dbContext.Entry(user).State = EntityState.Modified; - _dbContext.SaveChanges(); - return Json(new { result = true }); } - return Json(new { error = "User does not exist" }); - } - catch (Exception ex) - { - return Json(new { error = ex.GetFullMessage(true) }); - } - } - - [HttpPost] - [ValidateAntiForgeryToken] - public IActionResult EditTokenName(int tokenId, string name) - { - try - { - User user = UserHelper.GetUser(_dbContext, User.Identity.Name); - if (user != null) + else { - //AuthToken foundToken = _dbContext.AuthTokens.Where(d => d.UserId == user.UserId && d.AuthTokenId == tokenId).FirstOrDefault(); - //if (foundToken != null) - //{ - // foundToken.Name = name; - // _dbContext.Entry(foundToken).State = EntityState.Modified; - // _dbContext.SaveChanges(); - - // return Json(new { result = new { name = name } }); - //} - return Json(new { error = "Authentication Token does not exist" }); + return Json(new { error = result.Message }); } - return Json(new { error = "User does not exist" }); - } - catch (Exception ex) - { - return Json(new { error = ex.GetFullMessage(true) }); - } - } - - [HttpPost] - [ValidateAntiForgeryToken] - public IActionResult DeleteToken(int tokenId) - { - try - { - User user = UserHelper.GetUser(_dbContext, User.Identity.Name); - if (user != null) - { - //AuthToken foundToken = _dbContext.AuthTokens.Where(d => d.UserId == user.UserId && d.AuthTokenId == tokenId).FirstOrDefault(); - //if (foundToken != null) - //{ - // _dbContext.AuthTokens.Remove(foundToken); - // user.AuthTokens.Remove(foundToken); - // _dbContext.Entry(user).State = EntityState.Modified; - // _dbContext.SaveChanges(); - - // return Json(new { result = true }); - //} - return Json(new { error = "Authentication Token does not exist" }); - } - return Json(new { error = "User does not exist" }); } catch (Exception ex) { diff --git a/Teknik/Areas/User/Models/AuthToken.cs b/Teknik/Areas/User/Models/AuthToken.cs new file mode 100644 index 0000000..d93f491 --- /dev/null +++ b/Teknik/Areas/User/Models/AuthToken.cs @@ -0,0 +1,16 @@ +using System; +using Teknik.Utilities.Attributes; + +namespace Teknik.Areas.Users.Models +{ + public class AuthToken + { + public string AuthTokenId { get; set; } + + public string Name { get; set; } + + public string Token { get; set; } + + public DateTime? LastUsed { get; set; } + } +} diff --git a/Teknik/Areas/User/Models/IdentityUserInfo.cs b/Teknik/Areas/User/Models/IdentityUserInfo.cs index 5bbbb81..a4a977d 100644 --- a/Teknik/Areas/User/Models/IdentityUserInfo.cs +++ b/Teknik/Areas/User/Models/IdentityUserInfo.cs @@ -10,6 +10,8 @@ namespace Teknik.Areas.Users.Models { public class IdentityUserInfo { + public string Username { get; set; } + public DateTime? CreationDate { get; set; } public DateTime? LastSeen { get; set; } @@ -30,6 +32,10 @@ namespace Teknik.Areas.Users.Models public IdentityUserInfo(IEnumerable claims) { + if (claims.FirstOrDefault(c => c.Type == "username") != null) + { + RecoveryEmail = claims.FirstOrDefault(c => c.Type == "username").Value; + } if (claims.FirstOrDefault(c => c.Type == "creation-date") != null) { if (DateTime.TryParse(claims.FirstOrDefault(c => c.Type == "creation-date").Value, out var dateTime)) @@ -72,6 +78,10 @@ namespace Teknik.Areas.Users.Models public IdentityUserInfo(JObject info) { + if (info["username"] != null) + { + Username = info["username"].ToString(); + } if (info["creation-date"] != null) { if (DateTime.TryParse(info["creation-date"].ToString(), out var dateTime)) diff --git a/Teknik/Areas/User/Utility/IdentityHelper.cs b/Teknik/Areas/User/Utility/IdentityHelper.cs index 4e8e54f..6846886 100644 --- a/Teknik/Areas/User/Utility/IdentityHelper.cs +++ b/Teknik/Areas/User/Utility/IdentityHelper.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Security.Claims; using System.Threading.Tasks; using Teknik.Areas.Users.Models; using Teknik.Configuration; @@ -171,6 +172,30 @@ namespace Teknik.Areas.Users.Utility throw new Exception(result.Message); } + public static async Task GetIdentityUserInfoByToken(Config config, string token) + { + var manageUrl = CreateUrl(config, $"Manage/GetUserInfoByAuthToken?authToken={token}"); + + var result = await Get(config, manageUrl); + if (result.Success) + { + return new IdentityUserInfo((JObject)result.Data); + } + throw new Exception(result.Message); + } + + public static async Task GetIdentityUserClaims(Config config, string username) + { + var manageUrl = CreateUrl(config, $"Manage/GetUserClaims?username={username}"); + + var result = await Get(config, manageUrl); + if (result.Success) + { + return ((JArray)result.Data).ToObject(); + } + throw new Exception(result.Message); + } + public static async Task CheckPassword(Config config, string username, string password) { var manageUrl = CreateUrl(config, $"Manage/CheckPassword"); @@ -446,5 +471,67 @@ namespace Teknik.Areas.Users.Utility }); return response; } + + public static async Task GetAuthToken(Config config, string authTokenId) + { + var manageUrl = CreateUrl(config, $"Manage/GetAuthToken?authTokenId={authTokenId}"); + + var result = await Get(config, manageUrl); + if (result.Success) + { + return ((JObject)result.Data).ToObject(); + } + throw new Exception(result.Message); + } + + public static async Task GetAuthTokens(Config config, string username) + { + var manageUrl = CreateUrl(config, $"Manage/GetAuthTokens?username={username}"); + + var result = await Get(config, manageUrl); + if (result.Success) + { + return ((JArray)result.Data).ToObject(); + } + throw new Exception(result.Message); + } + + public static async Task CreateAuthToken(Config config, string username, string name) + { + var manageUrl = CreateUrl(config, $"Manage/CreateAuthToken"); + + var response = await Post(config, manageUrl, + new + { + username = username, + name = name + }); + return response; + } + + public static async Task EditAuthToken(Config config, string authTokenId, string name) + { + var manageUrl = CreateUrl(config, $"Manage/EditAuthToken"); + + var response = await Post(config, manageUrl, + new + { + authTokenId = authTokenId, + name = name + }); + return response; + } + + public static async Task DeleteAuthToken(Config config, string authTokenId) + { + var manageUrl = CreateUrl(config, $"Manage/DeleteAuthToken"); + + var response = await Post(config, manageUrl, + new + { + authTokenId = authTokenId + }); + return response; + } } } diff --git a/Teknik/Areas/User/ViewModels/AuthTokenSettingsViewModel.cs b/Teknik/Areas/User/ViewModels/AuthTokenSettingsViewModel.cs new file mode 100644 index 0000000..a7e4ec3 --- /dev/null +++ b/Teknik/Areas/User/ViewModels/AuthTokenSettingsViewModel.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Teknik.Areas.Users.ViewModels +{ + public class AuthTokenSettingsViewModel : SettingsViewModel + { + public List AuthTokens { get; set; } + + public AuthTokenSettingsViewModel() + { + AuthTokens = new List(); + } + } +} diff --git a/Teknik/Areas/User/ViewModels/AuthTokenViewModel.cs b/Teknik/Areas/User/ViewModels/AuthTokenViewModel.cs index 5db6167..54e2ee7 100644 --- a/Teknik/Areas/User/ViewModels/AuthTokenViewModel.cs +++ b/Teknik/Areas/User/ViewModels/AuthTokenViewModel.cs @@ -8,7 +8,7 @@ namespace Teknik.Areas.Users.ViewModels { public class AuthTokenViewModel : ViewModelBase { - public int AuthTokenId { get; set; } + public string AuthTokenId { get; set; } public string Name { get; set; } diff --git a/Teknik/Areas/User/ViewModels/DeveloperSettingsViewModel.cs b/Teknik/Areas/User/ViewModels/ClientSettingsViewModel.cs similarity index 55% rename from Teknik/Areas/User/ViewModels/DeveloperSettingsViewModel.cs rename to Teknik/Areas/User/ViewModels/ClientSettingsViewModel.cs index 5f91bbe..30b4487 100644 --- a/Teknik/Areas/User/ViewModels/DeveloperSettingsViewModel.cs +++ b/Teknik/Areas/User/ViewModels/ClientSettingsViewModel.cs @@ -5,15 +5,12 @@ using System.Threading.Tasks; namespace Teknik.Areas.Users.ViewModels { - public class DeveloperSettingsViewModel : SettingsViewModel + public class ClientSettingsViewModel : SettingsViewModel { - - public List AuthTokens { get; set; } public List Clients { get; set; } - public DeveloperSettingsViewModel() + public ClientSettingsViewModel() { - AuthTokens = new List(); Clients = new List(); } } diff --git a/Teknik/Areas/User/Views/User/Settings/AuthTokenSettings.cshtml b/Teknik/Areas/User/Views/User/Settings/AuthTokenSettings.cshtml new file mode 100644 index 0000000..392d84a --- /dev/null +++ b/Teknik/Areas/User/Views/User/Settings/AuthTokenSettings.cshtml @@ -0,0 +1,76 @@ +@model Teknik.Areas.Users.ViewModels.AuthTokenSettingsViewModel + +@using Teknik.Areas.Users.ViewModels + +@{ + Layout = "~/Areas/User/Views/User/Settings/Settings.cshtml"; +} + + + +
+
+

Auth Tokens

+
+
+
+
+
+ +
+
+
+
    + @if (Model.AuthTokens.Any()) + { + foreach (AuthTokenViewModel authToken in Model.AuthTokens) + { + @await Html.PartialAsync("Settings/AuthTokenView", authToken) + } + } + else + { +
  • No Auth Tokens
  • + } +
+
+
+
+ + + + + diff --git a/Teknik/Areas/User/Views/User/Settings/AuthToken.cshtml b/Teknik/Areas/User/Views/User/Settings/AuthTokenView.cshtml similarity index 85% rename from Teknik/Areas/User/Views/User/Settings/AuthToken.cshtml rename to Teknik/Areas/User/Views/User/Settings/AuthTokenView.cshtml index 239c202..61947ab 100644 --- a/Teknik/Areas/User/Views/User/Settings/AuthToken.cshtml +++ b/Teknik/Areas/User/Views/User/Settings/AuthTokenView.cshtml @@ -2,8 +2,8 @@
  • - - + +

    @Model.Name

    @if (Model.LastDateUsed.HasValue) diff --git a/Teknik/Areas/User/Views/User/Settings/DeveloperSettings.cshtml b/Teknik/Areas/User/Views/User/Settings/ClientSettings.cshtml similarity index 96% rename from Teknik/Areas/User/Views/User/Settings/DeveloperSettings.cshtml rename to Teknik/Areas/User/Views/User/Settings/ClientSettings.cshtml index 67c519d..c5ca4e8 100644 --- a/Teknik/Areas/User/Views/User/Settings/DeveloperSettings.cshtml +++ b/Teknik/Areas/User/Views/User/Settings/ClientSettings.cshtml @@ -1,4 +1,4 @@ -@model Teknik.Areas.Users.ViewModels.DeveloperSettingsViewModel +@model Teknik.Areas.Users.ViewModels.ClientSettingsViewModel @using Teknik.Areas.Users.ViewModels @@ -6,7 +6,7 @@ Layout = "~/Areas/User/Views/User/Settings/Settings.cshtml"; } - +