Browse Source

Added Auth Tokens for direct API access

master
Teknikode 7 months ago
parent
commit
2233d6c3be
  1. 4
      IdentityServer/App_Data/version.json
  2. 4
      IdentityServer/App_Data/version.template.json
  3. 29
      IdentityServer/ApplicationDbContext.cs
  4. 153
      IdentityServer/Controllers/ManageController.cs
  5. 328
      IdentityServer/Data/Migrations/ApplicationDb/20211206044736_AuthToken.Designer.cs
  6. 42
      IdentityServer/Data/Migrations/ApplicationDb/20211206044736_AuthToken.cs
  7. 59
      IdentityServer/Data/Migrations/ApplicationDb/ApplicationDbContextModelSnapshot.cs
  8. 1
      IdentityServer/IdentityServer.csproj
  9. 4
      IdentityServer/Models/ApplicationUser.cs
  10. 24
      IdentityServer/Models/AuthToken.cs
  11. 13
      IdentityServer/Models/Manage/CreateAuthTokenModel.cs
  12. 12
      IdentityServer/Models/Manage/DeleteAuthTokenModel.cs
  13. 10
      IdentityServer/Models/Manage/EditAuthTokenModel.cs
  14. 17
      Teknik/App_Data/endpointMappings.json
  15. 1
      Teknik/Areas/API/V1/Controllers/PasteAPIv1Controller.cs
  16. 251
      Teknik/Areas/User/Controllers/UserController.cs
  17. 16
      Teknik/Areas/User/Models/AuthToken.cs
  18. 10
      Teknik/Areas/User/Models/IdentityUserInfo.cs
  19. 87
      Teknik/Areas/User/Utility/IdentityHelper.cs
  20. 14
      Teknik/Areas/User/ViewModels/AuthTokenSettingsViewModel.cs
  21. 2
      Teknik/Areas/User/ViewModels/AuthTokenViewModel.cs
  22. 7
      Teknik/Areas/User/ViewModels/ClientSettingsViewModel.cs
  23. 76
      Teknik/Areas/User/Views/User/Settings/AuthTokenSettings.cshtml
  24. 4
      Teknik/Areas/User/Views/User/Settings/AuthTokenView.cshtml
  25. 6
      Teknik/Areas/User/Views/User/Settings/ClientSettings.cshtml
  26. 6
      Teknik/Areas/User/Views/User/Settings/Settings.cshtml
  27. 0
      Teknik/Content/User/Settings/Client.css
  28. 196
      Teknik/Scripts/User/AuthTokenSettings.js
  29. 0
      Teknik/Scripts/User/ClientSettings.js
  30. 97
      Teknik/Security/AuthTokenAuthenticationHandler.cs
  31. 13
      Teknik/Security/AuthTokenSchemeOptions.cs
  32. 11
      Teknik/Startup.cs
  33. 14
      Teknik/bundleconfig.json
  34. 17
      Utilities/Attributes/EntityAttribute.cs
  35. 2
      Utilities/Constants.cs

4
IdentityServer/App_Data/version.json

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
{
"version": "5.1.0",
"hash": "37fc3ca56005b7eca9143ae4c74f4163e9417856"
}

4
IdentityServer/App_Data/version.template.json

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
{
"version": "{{git_ver}}",
"hash": "{{git_hash}}"
}

29
IdentityServer/ApplicationDbContext.cs

@ -1,11 +1,40 @@ @@ -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<ApplicationUser>
{
public DbSet<AuthToken> AuthTokens { get; set; }
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// User
modelBuilder.Entity<ApplicationUser>().HasMany(u => u.AuthTokens).WithOne(t => t.ApplicationUser).HasForeignKey(t => t.ApplicationUserId);
// Auth Tokens
modelBuilder.Entity<AuthToken>().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);
}
}
}
}
}
}

153
IdentityServer/Controllers/ManageController.cs

@ -139,6 +139,36 @@ namespace Teknik.IdentityServer.Controllers @@ -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<IActionResult> GetUserClaims(string username, [FromServices] IUserClaimsPrincipalFactory<ApplicationUser> 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<IActionResult> CheckPassword(CheckPasswordModel model)
{
@ -672,6 +702,109 @@ namespace Teknik.IdentityServer.Controllers @@ -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<IActionResult> 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 @@ -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<ApplicationUser>(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))

328
IdentityServer/Data/Migrations/ApplicationDb/20211206044736_AuthToken.Designer.cs generated

@ -0,0 +1,328 @@ @@ -0,0 +1,328 @@
// <auto-generated />
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<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("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<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderKey")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("RoleId")
.HasColumnType("nvarchar(450)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("Name")
.HasColumnType("nvarchar(450)");
b.Property<string>("Value")
.HasColumnType("nvarchar(max)");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Teknik.IdentityServer.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<int>("AccountStatus")
.HasColumnType("int");
b.Property<int>("AccountType")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("CreationDate")
.HasColumnType("datetime2");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("bit");
b.Property<DateTime>("LastEdit")
.HasColumnType("datetime2");
b.Property<DateTime>("LastSeen")
.HasColumnType("datetime2");
b.Property<bool>("LockoutEnabled")
.HasColumnType("bit");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetimeoffset");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("PGPPublicKey")
.HasColumnType("nvarchar(max)");
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("PhoneNumber")
.HasColumnType("nvarchar(max)");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit");
b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit");
b.Property<string>("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<Guid>("AuthTokenId")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("ApplicationUserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("Name")
.HasColumnType("nvarchar(max)");
b.Property<string>("Token")
.HasColumnType("nvarchar(max)")
.HasAnnotation("CaseSensitive", true);
b.HasKey("AuthTokenId");
b.HasIndex("ApplicationUserId");
b.ToTable("AuthTokens");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Teknik.IdentityServer.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Teknik.IdentityServer.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", 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<string>", 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
}
}
}

42
IdentityServer/Data/Migrations/ApplicationDb/20211206044736_AuthToken.cs

@ -0,0 +1,42 @@ @@ -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<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(max)", nullable: true),
ApplicationUserId = table.Column<string>(type: "nvarchar(450)", nullable: true),
Token = table.Column<string>(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");
}
}
}

59
IdentityServer/Data/Migrations/ApplicationDb/ApplicationDbContextModelSnapshot.cs

@ -16,13 +16,12 @@ namespace Teknik.IdentityServer.Data.Migrations.ApplicationDb @@ -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<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("nvarchar(450)");
b.Property<string>("ConcurrencyStamp")
@ -154,7 +153,6 @@ namespace Teknik.IdentityServer.Data.Migrations.ApplicationDb @@ -154,7 +153,6 @@ namespace Teknik.IdentityServer.Data.Migrations.ApplicationDb
modelBuilder.Entity("Teknik.IdentityServer.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("nvarchar(450)");
b.Property<int>("AccessFailedCount")
@ -235,12 +233,36 @@ namespace Teknik.IdentityServer.Data.Migrations.ApplicationDb @@ -235,12 +233,36 @@ namespace Teknik.IdentityServer.Data.Migrations.ApplicationDb
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Teknik.IdentityServer.Models.AuthToken", b =>
{
b.Property<Guid>("AuthTokenId")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("ApplicationUserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("Name")
.HasColumnType("nvarchar(max)");
b.Property<string>("Token")
.HasColumnType("nvarchar(max)")
.HasAnnotation("CaseSensitive", true);
b.HasKey("AuthTokenId");
b.HasIndex("ApplicationUserId");
b.ToTable("AuthTokens");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
@ -248,7 +270,8 @@ namespace Teknik.IdentityServer.Data.Migrations.ApplicationDb @@ -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<string>", b =>
@ -256,7 +279,8 @@ namespace Teknik.IdentityServer.Data.Migrations.ApplicationDb @@ -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<string>", b =>
@ -264,12 +288,14 @@ namespace Teknik.IdentityServer.Data.Migrations.ApplicationDb @@ -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<string>", b =>
@ -277,7 +303,22 @@ namespace Teknik.IdentityServer.Data.Migrations.ApplicationDb @@ -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
}

1
IdentityServer/IdentityServer.csproj

@ -15,7 +15,6 @@ @@ -15,7 +15,6 @@
<ItemGroup>
<Folder Include="Middleware\" />
<Folder Include="App_Data\" />
<Folder Include="wwwroot\" />
</ItemGroup>

4
IdentityServer/Models/ApplicationUser.cs

@ -1,4 +1,5 @@ @@ -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 @@ -23,6 +24,8 @@ namespace Teknik.IdentityServer.Models
public DateTime LastEdit { get; set; }
public virtual ICollection<AuthToken> AuthTokens { get; set; }
public ApplicationUser() : base()
{
Init();
@ -41,6 +44,7 @@ namespace Teknik.IdentityServer.Models @@ -41,6 +44,7 @@ namespace Teknik.IdentityServer.Models
AccountType = AccountType.Basic;
AccountStatus = AccountStatus.Active;
PGPPublicKey = null;
AuthTokens = new List<AuthToken>();
}
public List<Claim> ToClaims()

24
IdentityServer/Models/AuthToken.cs

@ -0,0 +1,24 @@ @@ -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; }
}
}

13
IdentityServer/Models/Manage/CreateAuthTokenModel.cs

@ -0,0 +1,13 @@ @@ -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; }
}
}

12
IdentityServer/Models/Manage/DeleteAuthTokenModel.cs

@ -0,0 +1,12 @@ @@ -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; }
}
}

10
IdentityServer/Models/Manage/EditAuthTokenModel.cs

@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
using System;
namespace Teknik.IdentityServer.Models.Manage
{
public class EditAuthTokenModel
{
public string AuthTokenId { get; set; }
public string Name { get; set; }
}
}

17
Teknik/App_Data/endpointMappings.json

@ -1090,14 +1090,25 @@ @@ -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"
}
},
{

1
Teknik/Areas/API/V1/Controllers/PasteAPIv1Controller.cs

@ -25,7 +25,6 @@ namespace Teknik.Areas.API.V1.Controllers @@ -25,7 +25,6 @@ namespace Teknik.Areas.API.V1.Controllers
public PasteAPIv1Controller(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
[HttpPost]
[AllowAnonymous]
[TrackPageView]
public IActionResult Paste(PasteAPIv1Model model)
{

251
Teknik/Areas/User/Controllers/UserController.cs

@ -418,17 +418,6 @@ namespace Teknik.Areas.Users.Controllers @@ -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<AuthTokenViewModel>();
//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 @@ -444,32 +433,22 @@ namespace Teknik.Areas.Users.Controllers
}
[TrackPageView]
public async Task<IActionResult> DeveloperSettings()
public async Task<IActionResult> 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<AuthTokenViewModel>();
model.Clients = new List<ClientViewModel>();
//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 @@ -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<IActionResult> 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<AuthTokenViewModel>();
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 @@ -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,73 +1124,32 @@ namespace Teknik.Areas.Users.Controllers @@ -1124,73 +1124,32 @@ namespace Teknik.Areas.Users.Controllers
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult ClearTrustedDevices()
public async Task<IActionResult> 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<TrustedDevice> 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();
// return Json(new { result = true });
//}
return Json(new { error = "User does not allow trusted devices" });
}
return Json(new { error = "User does not exist" });
}
catch (Exception ex)
{
return Json(new { error = ex.GetFullMessage(true) });
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> GenerateToken(string name, [FromServices] ICompositeViewEngine viewEngine)
{
try
{
User user = UserHelper.GetUser(_dbContext, User.Identity.Name);
if (user != null)
{
//string newTokenStr = UserHelper.GenerateAuthToken(_dbContext, user.Username);
if (string.IsNullOrEmpty(name))
return Json(new { error = "You must enter an auth token name" });
//if (!string.IsNullOrEmpty(newTokenStr))
//{
// AuthToken token = new AuthToken();
// token.UserId = user.UserId;
// token.HashedToken = SHA256.Hash(newTokenStr);
// token.Name = name;
// Validate the code with the identity server
var result = await IdentityHelper.CreateAuthToken(
_config,
User.Identity.Name,
name);
// _dbContext.AuthTokens.Add(token);
// _dbContext.SaveChanges();
if (result.Success)
{
var authToken = (JObject)result.Data;
// AuthTokenViewModel model = new AuthTokenViewModel();
// model.AuthTokenId = token.AuthTokenId;
// model.Name = token.Name;
// model.LastDateUsed = token.LastDateUsed;
AuthTokenViewModel model = new AuthTokenViewModel();
model.AuthTokenId = authToken["authTokenId"].ToString();
model.Name = name;
// string renderedView = await RenderPartialViewToString(viewEngine, "~/Areas/User/Views/User/Settings/AuthToken.cshtml", model);
string renderedView = await RenderPartialViewToString(viewEngine, "~/Areas/User/Views/User/Settings/AuthTokenView.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 = 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)
{
@ -1200,56 +1159,55 @@ namespace Teknik.Areas.Users.Controllers @@ -1200,56 +1159,55 @@ namespace Teknik.Areas.Users.Controllers
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult RevokeAllTokens()
public async Task<IActionResult> GetAuthToken(string authTokenId)
{
try
AuthToken foundAuthToken = await IdentityHelper.GetAuthToken(_config, authTokenId);
if (foundAuthToken != null)
{
User user = UserHelper.GetUser(_dbContext, User.Identity.Name);
if (user != null)
AuthTokenViewModel model = new AuthTokenViewModel()
{
//user.AuthTokens.Clear();
//List<AuthToken> 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();
AuthTokenId = foundAuthToken.AuthTokenId.ToString(),
Name = foundAuthToken.Name
};
return Json(new { result = true });
}
return Json(new { error = "User does not exist" });
}
catch (Exception ex)
{
return Json(new { error = ex.GetFullMessage(true) });
return Json(new { result = true, authToken = model });
}
return new StatusCodeResult(StatusCodes.Status403Forbidden);
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult EditTokenName(int tokenId, string name)
public async Task<IActionResult> 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)
{
//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" });
var authToken = (JObject)result.Data;
AuthTokenViewModel model = new AuthTokenViewModel();
model.AuthTokenId = authTokenId;
model.Name = name;
string renderedView = await RenderPartialViewToString(viewEngine, "~/Areas/User/Views/User/Settings/AuthTokenView.cshtml", model);
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)
{
@ -1259,26 +1217,19 @@ namespace Teknik.Areas.Users.Controllers @@ -1259,26 +1217,19 @@ namespace Teknik.Areas.Users.Controllers
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult DeleteToken(int tokenId)
public async Task<IActionResult> 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)
{
//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 { result = true });
}
else
{
return Json(new { error = result.Message });
}
return Json(new { error = "User does not exist" });
}
catch (Exception ex)
{

16
Teknik/Areas/User/Models/AuthToken.cs

@ -0,0 +1,16 @@ @@ -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; }
}
}

10
Teknik/Areas/User/Models/IdentityUserInfo.cs

@ -10,6 +10,8 @@ namespace Teknik.Areas.Users.Models @@ -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 @@ -30,6 +32,10 @@ namespace Teknik.Areas.Users.Models
public IdentityUserInfo(IEnumerable<Claim> 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 @@ -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))

87
Teknik/Areas/User/Utility/IdentityHelper.cs

@ -6,6 +6,7 @@ using System; @@ -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 @@ -171,6 +172,30 @@ namespace Teknik.Areas.Users.Utility
throw new Exception(result.Message);
}
public static async Task<IdentityUserInfo> 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<Claim[]> 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<Claim[]>();
}
throw new Exception(result.Message);
}
public static async Task<bool> CheckPassword(Config config, string username, string password)
{
var manageUrl = CreateUrl(config, $"Manage/CheckPassword");
@ -446,5 +471,67 @@ namespace Teknik.Areas.Users.Utility @@ -446,5 +471,67 @@ namespace Teknik.Areas.Users.Utility
});
return response;
}
public static async Task<AuthToken> 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<AuthToken>();
}
throw new Exception(result.Message);
}
public static async Task<AuthToken[]> 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<AuthToken[]>();
}
throw new Exception(result.Message);
}
public static async Task<IdentityResult> 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<IdentityResult> 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<IdentityResult> DeleteAuthToken(Config config, string authTokenId)
{
var manageUrl = CreateUrl(config, $"Manage/DeleteAuthToken");
var response = await Post(config, manageUrl,
new
{
authTokenId = authTokenId
});
return response;
}
}
}

14
Teknik/Areas/User/ViewModels/AuthTokenSettingsViewModel.cs

@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace Teknik.Areas.Users.ViewModels
{
public class AuthTokenSettingsViewModel : SettingsViewModel
{
public List<AuthTokenViewModel> AuthTokens { get; set; }
public AuthTokenSettingsViewModel()
{
AuthTokens = new List<AuthTokenViewModel>();
}
}
}

2
Teknik/Areas/User/ViewModels/AuthTokenViewModel.cs

@ -8,7 +8,7 @@ namespace Teknik.Areas.Users.ViewModels @@ -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; }