mirror of
https://git.teknik.io/Teknikode/Teknik.git
synced 2023-08-02 14:16:22 +02:00
Added Auth Tokens for direct API access
This commit is contained in:
parent
37fc3ca560
commit
2233d6c3be
4
IdentityServer/App_Data/version.json
Normal file
4
IdentityServer/App_Data/version.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"version": "5.1.0",
|
||||
"hash": "37fc3ca56005b7eca9143ae4c74f4163e9417856"
|
||||
}
|
4
IdentityServer/App_Data/version.template.json
Normal file
4
IdentityServer/App_Data/version.template.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"version": "{{git_ver}}",
|
||||
"hash": "{{git_hash}}"
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
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
|
||||
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
Normal file
328
IdentityServer/Data/Migrations/ApplicationDb/20211206044736_AuthToken.Designer.cs
generated
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
}
|
||||
|
@ -15,7 +15,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Middleware\" />
|
||||
<Folder Include="App_Data\" />
|
||||
<Folder Include="wwwroot\" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -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<AuthToken> 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<AuthToken>();
|
||||
}
|
||||
|
||||
public List<Claim> ToClaims()
|
||||
|
24
IdentityServer/Models/AuthToken.cs
Normal file
24
IdentityServer/Models/AuthToken.cs
Normal file
@ -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
Normal file
13
IdentityServer/Models/Manage/CreateAuthTokenModel.cs
Normal file
@ -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
Normal file
12
IdentityServer/Models/Manage/DeleteAuthTokenModel.cs
Normal file
@ -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
Normal file
10
IdentityServer/Models/Manage/EditAuthTokenModel.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace Teknik.IdentityServer.Models.Manage
|
||||
{
|
||||
public class EditAuthTokenModel
|
||||
{
|
||||
public string AuthTokenId { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
[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
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
//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<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();
|
||||
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<IActionResult> GenerateToken(string name, [FromServices] ICompositeViewEngine viewEngine)
|
||||
public async Task<IActionResult> 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<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)
|
||||
{
|
||||
//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,86 +1217,20 @@ namespace Teknik.Areas.Users.Controllers
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public IActionResult RevokeAllTokens()
|
||||
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)
|
||||
{
|
||||
//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();
|
||||
|
||||
return Json(new { result = true });
|
||||
}
|
||||
return Json(new { error = "User does not exist" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
else
|
||||
{
|
||||
return Json(new { error = ex.GetFullMessage(true) });
|
||||
return Json(new { error = result.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public IActionResult EditTokenName(int tokenId, string name)
|
||||
{
|
||||
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)
|
||||
//{
|
||||
// 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 = "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)
|
||||
{
|
||||
return Json(new { error = ex.GetFullMessage(true) });
|
||||
|
16
Teknik/Areas/User/Models/AuthToken.cs
Normal file
16
Teknik/Areas/User/Models/AuthToken.cs
Normal file
@ -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,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<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
|
||||
|
||||
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))
|
||||
|
@ -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<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
|
||||
});
|
||||
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
Normal file
14
Teknik/Areas/User/ViewModels/AuthTokenSettingsViewModel.cs
Normal file
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
@ -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; }
|
||||
|
||||
|
@ -5,15 +5,12 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Teknik.Areas.Users.ViewModels
|
||||
{
|
||||
public class DeveloperSettingsViewModel : SettingsViewModel
|
||||
public class ClientSettingsViewModel : SettingsViewModel
|
||||
{
|
||||
|
||||
public List<AuthTokenViewModel> AuthTokens { get; set; }
|
||||
public List<ClientViewModel> Clients { get; set; }
|
||||
|
||||
public DeveloperSettingsViewModel()
|
||||
public ClientSettingsViewModel()
|
||||
{
|
||||
AuthTokens = new List<AuthTokenViewModel>();
|
||||
Clients = new List<ClientViewModel>();
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
@model Teknik.Areas.Users.ViewModels.AuthTokenSettingsViewModel
|
||||
|
||||
@using Teknik.Areas.Users.ViewModels
|
||||
|
||||
@{
|
||||
Layout = "~/Areas/User/Views/User/Settings/Settings.cshtml";
|
||||
}
|
||||
|
||||
<script>
|
||||
var createAuthTokenURL = '@Url.SubRouteUrl("account", "User.Action", new { action = "CreateAuthToken" })';
|
||||
var editAuthTokenURL = '@Url.SubRouteUrl("account", "User.Action", new { action = "EditAuthToken" })';
|
||||
var deleteAuthTokenURL = '@Url.SubRouteUrl("account", "User.Action", new { action = "DeleteAuthToken" })';
|
||||
var getAuthTokenURL = '@Url.SubRouteUrl("account", "User.Action", new { action = "GetAuthToken" })';
|
||||
</script>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<h2>Auth Tokens</h2>
|
||||
<hr />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<span class="pull-right"><button type="button" class="btn btn-default" id="createAuthToken">Create Auth Token</button></span>
|
||||
<div class="clearfix"></div>
|
||||
<br />
|
||||
<div id="authTokens">
|
||||
<ul class="list-group" id="authTokenList">
|
||||
@if (Model.AuthTokens.Any())
|
||||
{
|
||||
foreach (AuthTokenViewModel authToken in Model.AuthTokens)
|
||||
{
|
||||
@await Html.PartialAsync("Settings/AuthTokenView", authToken)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<li class="list-group-item text-center" id="noAuthTokens">No Auth Tokens</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal fade" id="authTokenModal" tabindex="-1" role="dialog" aria-labelledby="authTokenModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header modal-header-default">
|
||||
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="authTokenModalLabel">Auth Token Information</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" class="form-control" id="authTokenId" name="authTokenId" />
|
||||
<div class="row">
|
||||
<div class="col-sm-12 text-center">
|
||||
<div id="authTokenStatus">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="authTokenName">Auth Token Name <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="authTokenName" name="authTokenName" data-val-required="The Auth Token Name field is required." data-val="true" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<button class="btn btn-primary pull-right hidden authTokenSubmit" id="authTokenCreateSubmit" type="submit" name="authTokenCreateSubmit">Create Auth Token</button>
|
||||
<button class="btn btn-primary pull-right hidden authTokenSubmit" id="authTokenEditSubmit" type="submit" name="authTokenEditSubmit">Save Auth Token</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<bundle src="js/user.settings.authToken.min.js" append-version="true"></bundle>
|
@ -2,8 +2,8 @@
|
||||
|
||||
<li class="list-group-item" id="authToken_@Model.AuthTokenId">
|
||||
<div class="btn-group btn-group-sm pull-right" role="group" aria-label="...">
|
||||
<button type="button" class="btn btn-default editAuthToken" id="editAuthToken_@Model.AuthTokenId" data-authId="@Model.AuthTokenId">Edit</button>
|
||||
<button type="button" class="btn btn-danger text-danger deleteAuthToken" id="deleteAuthToken_@Model.AuthTokenId" data-authId="@Model.AuthTokenId">Delete</button>
|
||||
<button type="button" class="btn btn-default editAuthToken" id="editAuthToken_@Model.AuthTokenId" data-authTokenId="@Model.AuthTokenId">Edit</button>
|
||||
<button type="button" class="btn btn-danger text-danger deleteAuthToken" id="deleteAuthToken_@Model.AuthTokenId" data-authTokenId="@Model.AuthTokenId">Delete</button>
|
||||
</div>
|
||||
<h4 class="list-group-item-heading" id="authTokenName_@Model.AuthTokenId">@Model.Name</h4>
|
||||
@if (Model.LastDateUsed.HasValue)
|
@ -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";
|
||||
}
|
||||
|
||||
<bundle src="css/user.settings.developer.min.css" append-version="true"></bundle>
|
||||
<bundle src="css/user.settings.client.min.css" append-version="true"></bundle>
|
||||
|
||||
<script>
|
||||
var createClientURL = '@Url.SubRouteUrl("account", "User.Action", new { action = "CreateClient" })';
|
||||
@ -133,4 +133,4 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<bundle src="js/user.settings.developer.min.js" append-version="true"></bundle>
|
||||
<bundle src="js/user.settings.client.min.js" append-version="true"></bundle>
|
@ -14,7 +14,7 @@
|
||||
<!--left col-->
|
||||
<div class="panel panel-default">
|
||||
<ul class="list-group">
|
||||
<div class="panel-heading text-center"><strong>Personal Settings</strong></div>
|
||||
<div class="panel-heading text-center"><h5>Personal Settings</h5></div>
|
||||
<a href="@Url.SubRouteUrl("account", "User.ProfileSettings")" class="list-group-item @(Model.Page == "Profile" ? "active" : string.Empty)">Profile</a>
|
||||
<a href="@Url.SubRouteUrl("account", "User.AccountSettings")" class="list-group-item @(Model.Page == "Account" ? "active" : string.Empty)">Account</a>
|
||||
@if (Config.BillingConfig.Enabled)
|
||||
@ -29,7 +29,9 @@
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<ul class="list-group">
|
||||
<a href="@Url.SubRouteUrl("account", "User.DeveloperSettings")" class="list-group-item @(Model.Page == "Developer" ? "active" : string.Empty)">Developer Settings</a>
|
||||
<div class="panel-heading text-center"><h5>Developer Settings</h5></div>
|
||||
<a href="@Url.SubRouteUrl("account", "User.ClientSettings")" class="list-group-item @(Model.Page == "Clients" ? "active" : string.Empty)">Clients</a>
|
||||
<a href="@Url.SubRouteUrl("account", "User.AuthTokenSettings")" class="list-group-item @(Model.Page == "AuthTokens" ? "active" : string.Empty)">Auth Tokens</a>
|
||||
</ul>
|
||||
</div>
|
||||
</div><!--/col-2-->
|
||||
|
196
Teknik/Scripts/User/AuthTokenSettings.js
Normal file
196
Teknik/Scripts/User/AuthTokenSettings.js
Normal file
@ -0,0 +1,196 @@
|
||||
/* globals createAuthTokenURL, getAuthTokenURL, editAuthTokenURL, deleteAuthTokenURL */
|
||||
$(document).ready(function () {
|
||||
|
||||
$('#AuthTokenModal').on('shown.bs.modal', function () {
|
||||
$("#authTokenStatus").css('display', 'none', 'important');
|
||||
$("#authTokenStatus").html('');
|
||||
|
||||
$('#authTokenName').focus();
|
||||
});
|
||||
|
||||
$('#authTokenModal').on('hide.bs.modal', function () {
|
||||
$("#authTokenStatus").css('display', 'none', 'important');
|
||||
$("#authTokenStatus").html('');
|
||||
|
||||
$(this).find('#authTokenCreateSubmit').addClass('hidden');
|
||||
$(this).find('#authTokenEditSubmit').addClass('hidden');
|
||||
|
||||
clearInputs('#authTokenModal');
|
||||
});
|
||||
|
||||
|
||||
$('#authTokenModal').find('#authTokenCreateSubmit').click(createAuthToken);
|
||||
$('#authTokenModal').find('#authTokenEditSubmit').click(editAuthTokenSave);
|
||||
|
||||
$("#createAuthToken").click(function () {
|
||||
$('#authTokenModal').find('#authTokenCreateSubmit').removeClass('hidden');
|
||||
$('#authTokenModal').find('#authTokenCreateSubmit').text('Create Auth Token');
|
||||
|
||||
$('#authTokenModal').modal('show');
|
||||
});
|
||||
|
||||
$(".editAuthToken").click(function () {
|
||||
var authTokenId = $(this).attr("data-authTokenId");
|
||||
editAuthToken(authTokenId);
|
||||
});
|
||||
|
||||
$(".deleteAuthToken").click(function () {
|
||||
var authTokenId = $(this).attr("data-authTokenId");
|
||||
deleteAuthToken(authTokenId);
|
||||
});
|
||||
});
|
||||
|
||||
function createAuthToken() {
|
||||
saveAuthTokenInfo(createAuthTokenURL, 'Create Auth Token', 'Creating Auth Token...', function (response) {
|
||||
$('#authTokenModal').modal('hide');
|
||||
|
||||
var dialog = bootbox.dialog({
|
||||
closeButton: false,
|
||||
buttons: {
|
||||
close: {
|
||||
label: 'Close',
|
||||
className: 'btn-primary',
|
||||
callback: function () {
|
||||
if ($('#noAuthTokens')) {
|
||||
$('#noAuthTokens').remove();
|
||||
}
|
||||
|
||||
var item = $(response.html);
|
||||
|
||||
processAuthTokenItem(item);
|
||||
|
||||
$('#authTokenList').append(item);
|
||||
}
|
||||
}
|
||||
},
|
||||
title: "Auth Token Secret",
|
||||
message: '<label for="authTokenSecret">Make sure to copy your auth token now.<br />You won\'t be able to see it again!</label><input type="text" class="form-control" id="authTokenSecret" value="' + response.token + '">',
|
||||
});
|
||||
|
||||
dialog.init(function () {
|
||||
dialog.find('#authTokenSecret').click(function () {
|
||||
$(this).select();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function processAuthTokenItem(item) {
|
||||
item.find('.editAuthToken').click(function () {
|
||||
var authTokenId = $(this).attr("data-authTokenId");
|
||||
editAuthToken(authTokenId);
|
||||
});
|
||||
|
||||
item.find('.deleteAuthToken').click(function () {
|
||||
var authTokenId = $(this).attr("data-authTokenId");
|
||||
deleteAuthToken(authTokenId);
|
||||
});
|
||||
}
|
||||
|
||||
function editAuthToken(authTokenId) {
|
||||
disableButton('.editAuthToken[data-authTokenId="' + authTokenId + '"]', 'Loading...');
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: getAuthTokenURL,
|
||||
data: AddAntiForgeryToken({ authTokenId: authTokenId }),
|
||||
success: function (data) {
|
||||
if (data.result) {
|
||||
$('#authTokenModal').find('#authTokenId').val(data.authToken.authTokenId);
|
||||
$('#authTokenModal').find('#authTokenName').val(data.authToken.name);
|
||||
|
||||
$('#authTokenModal').find('#authTokenEditSubmit').removeClass('hidden');
|
||||
$('#authTokenModal').find('#authTokenEditSubmit').text('Save Auth Token');
|
||||
|
||||
$('#authTokenModal').modal('show');
|
||||
|
||||
enableButton('.editAuthToken[data-authTokenId="' + authTokenId + '"]', 'Edit');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function editAuthTokenSave() {
|
||||
saveAuthTokenInfo(editAuthTokenURL, 'Save Auth Token', 'Saving Auth Token...', function (response) {
|
||||
$('#authToken_' + response.authTokenId).replaceWith(response.html);
|
||||
processAuthTokenItem($('#authToken_' + response.authTokenId));
|
||||
|
||||
$('#authTokenModal').modal('hide');
|
||||
|
||||
$("#top_msg").css('display', 'inline', 'important');
|
||||
$("#top_msg").html('<div class="alert alert-success alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>Successfully Saved Auth Token</div>');
|
||||
});
|
||||
}
|
||||
|
||||
function deleteAuthToken(authTokenId) {
|
||||
disableButton('.deleteAuthToken[data-authTokenId="' + authTokenId + '"]', 'Deleting...');
|
||||
bootbox.confirm({
|
||||
message: "<h2>Are you sure you want to delete this auth token?</h2><br /><br />This is <b>irreversable</b> and all applications using this token will stop working.",
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Delete',
|
||||
className: 'btn-danger'
|
||||
},
|
||||
cancel: {
|
||||
label: 'Cancel',
|
||||
className: 'btn-default'
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
if (result) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: deleteAuthTokenURL,
|
||||
data: AddAntiForgeryToken({ authTokenId: authTokenId }),
|
||||
success: function (response) {
|
||||
if (response.result) {
|
||||
$('#authToken_' + authTokenId).remove();
|
||||
if ($('#authTokenList li').length <= 0) {
|
||||
$('#authTokenList').html('<li class="list-group-item text-center" id="noAuthTokens">No Auth Tokens</li>');
|
||||
}
|
||||
}
|
||||
else {
|
||||
$("#top_msg").css('display', 'inline', 'important');
|
||||
$("#top_msg").html('<div class="alert alert-danger alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>' + parseErrorMessage(response) + '</div>');
|
||||
}
|
||||
}
|
||||
}).always(function () {
|
||||
enableButton('.deleteAuthToken[data-authTokenId="' + authTokenId + '"]', 'Delete');
|
||||
});
|
||||
} else {
|
||||
enableButton('.deleteAuthToken[data-authTokenId="' + authTokenId + '"]', 'Delete');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function saveAuthTokenInfo(url, submitText, submitActionText, callback) {
|
||||
var authTokenId, name;
|
||||
disableButton('.authTokenSubmit', submitActionText);
|
||||
|
||||
authTokenId = $('#authTokenModal').find('#authTokenId').val();
|
||||
name = $('#authTokenModal').find('#authTokenName').val();
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: AddAntiForgeryToken({ authTokenId: authTokenId, name: name }),
|
||||
success: function (response) {
|
||||
if (response.result) {
|
||||
if (callback) {
|
||||
callback(response);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$("#authTokenStatus").css('display', 'inline', 'important');
|
||||
$("#authTokenStatus").html('<div class="alert alert-danger alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>' + parseErrorMessage(response) + '</div>');
|
||||
}
|
||||
},
|
||||
error: function (response) {
|
||||
$("#authTokenStatus").css('display', 'inline', 'important');
|
||||
$("#authTokenStatus").html('<div class="alert alert-danger alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>' + parseErrorMessage(response.responseText) + '</div>');
|
||||
}
|
||||
}).always(function () {
|
||||
enableButton('.authTokenSubmit', submitText);
|
||||
});
|
||||
}
|
97
Teknik/Security/AuthTokenAuthenticationHandler.cs
Normal file
97
Teknik/Security/AuthTokenAuthenticationHandler.cs
Normal file
@ -0,0 +1,97 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Teknik.Areas.Users.Models;
|
||||
using Teknik.Areas.Users.Utility;
|
||||
using Teknik.Configuration;
|
||||
using Teknik.Data;
|
||||
|
||||
namespace Teknik.Security
|
||||
{
|
||||
public class AuthTokenAuthenticationHandler : AuthenticationHandler<AuthTokenSchemeOptions>
|
||||
{
|
||||
private readonly TeknikEntities _db;
|
||||
private readonly Config _config;
|
||||
public AuthTokenAuthenticationHandler(
|
||||
IOptionsMonitor<AuthTokenSchemeOptions> options,
|
||||
ILoggerFactory logger,
|
||||
UrlEncoder encoder,
|
||||
ISystemClock clock,
|
||||
TeknikEntities dbContext,
|
||||
Config config)
|
||||
: base(options, logger, encoder, clock)
|
||||
{
|
||||
_db = dbContext;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
// validation comes in here
|
||||
if (!Request.Headers.ContainsKey(HeaderNames.Authorization))
|
||||
{
|
||||
return AuthenticateResult.Fail("Header Not Found.");
|
||||
}
|
||||
|
||||
var header = Request.Headers[HeaderNames.Authorization].ToString();
|
||||
var tokenMatch = Regex.Match(header, $"AuthToken (?<token>.*)");
|
||||
|
||||
if (tokenMatch.Success)
|
||||
{
|
||||
// the token is captured in this group
|
||||
// as declared in the Regex
|
||||
var token = tokenMatch.Groups["token"].Value;
|
||||
|
||||
IdentityUserInfo userInfo = null;
|
||||
try
|
||||
{
|
||||
userInfo = await IdentityHelper.GetIdentityUserInfoByToken(_config, token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Exception Occured while Deserializing: " + ex);
|
||||
return AuthenticateResult.Fail("TokenParseException");
|
||||
}
|
||||
|
||||
// success branch
|
||||
// generate authTicket
|
||||
// authenticate the request
|
||||
if (userInfo != null)
|
||||
{
|
||||
// create claims array from the model
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim(ClaimTypes.Name, userInfo.Username),
|
||||
new Claim("scope", "teknik-api.read"),
|
||||
new Claim("scope", "teknik-api.write")
|
||||
};
|
||||
|
||||
// generate claimsIdentity on the name of the class
|
||||
var claimsIdentity = new ClaimsIdentity(claims, nameof(AuthTokenAuthenticationHandler));
|
||||
|
||||
// generate AuthenticationTicket from the Identity
|
||||
// and current authentication scheme
|
||||
var ticket = new AuthenticationTicket(new ClaimsPrincipal(claimsIdentity), this.Scheme.Name);
|
||||
|
||||
// pass on the ticket to the middleware
|
||||
return AuthenticateResult.Success(ticket);
|
||||
}
|
||||
}
|
||||
|
||||
// failure branch
|
||||
// return failure
|
||||
// with an optional message
|
||||
return AuthenticateResult.Fail("Invalid Authentication Token");
|
||||
}
|
||||
}
|
||||
}
|
13
Teknik/Security/AuthTokenSchemeOptions.cs
Normal file
13
Teknik/Security/AuthTokenSchemeOptions.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Teknik.Security
|
||||
{
|
||||
public class AuthTokenSchemeOptions : AuthenticationSchemeOptions
|
||||
{
|
||||
}
|
||||
}
|
@ -227,33 +227,34 @@ namespace Teknik
|
||||
};
|
||||
|
||||
options.Events.OnRemoteFailure = HandleOnRemoteFailure;
|
||||
});
|
||||
})
|
||||
.AddScheme<AuthTokenSchemeOptions, AuthTokenAuthenticationHandler>("AuthToken", "AuthToken", options => { });
|
||||
|
||||
services.AddAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("FullAPI", p =>
|
||||
{
|
||||
p.AddAuthenticationSchemes("Bearer");
|
||||
p.AddAuthenticationSchemes("AuthToken");
|
||||
p.AddAuthenticationSchemes("Bearer");
|
||||
p.RequireScope("teknik-api.read");
|
||||
p.RequireScope("teknik-api.write");
|
||||
});
|
||||
options.AddPolicy("ReadAPI", p =>
|
||||
{
|
||||
p.AddAuthenticationSchemes("Bearer");
|
||||
p.AddAuthenticationSchemes("AuthToken");
|
||||
p.AddAuthenticationSchemes("Bearer");
|
||||
p.RequireScope("teknik-api.read");
|
||||
});
|
||||
options.AddPolicy("WriteAPI", p =>
|
||||
{
|
||||
p.AddAuthenticationSchemes("Bearer");
|
||||
p.AddAuthenticationSchemes("AuthToken");
|
||||
p.AddAuthenticationSchemes("Bearer");
|
||||
p.RequireScope("teknik-api.write");
|
||||
});
|
||||
options.AddPolicy("AnyAPI", p =>
|
||||
{
|
||||
p.AddAuthenticationSchemes("Bearer");
|
||||
p.AddAuthenticationSchemes("AuthToken");
|
||||
p.AddAuthenticationSchemes("Bearer");
|
||||
p.RequireScope("teknik-api.read", "teknik-api.write");
|
||||
});
|
||||
});
|
||||
|
@ -274,15 +274,21 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"outputFileName": "./wwwroot/js/user.settings.developer.min.js",
|
||||
"outputFileName": "./wwwroot/js/user.settings.client.min.js",
|
||||
"inputFiles": [
|
||||
"./wwwroot/js/app/User/DeveloperSettings.js"
|
||||
"./wwwroot/js/app/User/ClientSettings.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"outputFileName": "./wwwroot/css/user.settings.developer.min.css",
|
||||
"outputFileName": "./wwwroot/css/user.settings.client.min.css",
|
||||
"inputFiles": [
|
||||
"./wwwroot/css/app/User/Settings/Developer.css"
|
||||
"./wwwroot/css/app/User/Settings/Client.css"
|
||||
]
|
||||
},
|
||||
{
|
||||
"outputFileName": "./wwwroot/js/user.settings.authToken.min.js",
|
||||
"inputFiles": [
|
||||
"./wwwroot/js/app/User/AuthTokenSettings.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
17
Utilities/Attributes/EntityAttribute.cs
Normal file
17
Utilities/Attributes/EntityAttribute.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
|
||||
namespace Teknik.Utilities.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
|
||||
public class CaseSensitiveAttribute : Attribute
|
||||
{
|
||||
public CaseSensitiveAttribute()
|
||||
{
|
||||
IsEnabled = true;
|
||||
}
|
||||
public bool IsEnabled { get; set; }
|
||||
}
|
||||
}
|
@ -16,6 +16,8 @@ namespace Teknik.Utilities
|
||||
|
||||
public const string NONCE_KEY = "Nonce";
|
||||
|
||||
public const string AUTH_TOKEN_MATCH = "AuthToken";
|
||||
|
||||
public const string NullIpAddress = "::1";
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user