diff --git a/.gitignore b/.gitignore index c90b1d4..893511b 100644 --- a/.gitignore +++ b/.gitignore @@ -262,6 +262,7 @@ __pycache__/ /Teknik/App_Data/MachineKey.config /Teknik/App_Data/ConnectionStrings.config /Teknik/App_Data/Config.json -/Teknik/appsettings.Production.json -/Teknik/appsettings.Development.json /Teknik/App_Data/version.json + +**/appsettings.*.json +**/tempkey.rsa \ No newline at end of file diff --git a/Configuration/Config.cs b/Configuration/Config.cs index 81ab369..1f594c2 100644 --- a/Configuration/Config.cs +++ b/Configuration/Config.cs @@ -19,38 +19,38 @@ namespace Teknik.Configuration private ReaderWriterLockSlim _ConfigFileRWLock; private JsonSerializerSettings _JsonSettings; - private bool _DevEnvironment; - private bool _Migrate; - private bool _UseCdn; - private string _Title; - private string _Description; - private string _Author; - private string _Host; - private string _SupportEmail; - private string _NoReplyEmail; - private string _BitcoinAddress; - private string _Salt1; - private string _Salt2; - private string _CdnHost; - private string _IPBlacklistFile; - private string _ReferrerBlacklistFile; - private List _PublicKeys; - private UserConfig _UserConfig; - private ContactConfig _ContactConfig; - private EmailConfig _EmailConfig; - private GitConfig _GitConfig; - private UploadConfig _UploadConfig; - private PasteConfig _PasteConfig; - private BlogConfig _BlogConfig; - private ApiConfig _ApiConfig; - private PodcastConfig _PodcastConfig; - private StreamConfig _StreamConfig; - private ShortenerConfig _ShortenerConfig; - private VaultConfig _VaultConfig; - private StatsConfig _StatsConfig; - private LoggingConfig _LoggingConfig; - private PiwikConfig _PiwikConfig; - private IRCConfig _IRCConfig; + private bool _DevEnvironment; + private bool _Migrate; + private bool _UseCdn; + private string _Title; + private string _Description; + private string _Author; + private string _Host; + private string _SupportEmail; + private string _NoReplyEmail; + private string _BitcoinAddress; + private string _Salt1; + private string _Salt2; + private string _CdnHost; + private string _IPBlacklistFile; + private string _ReferrerBlacklistFile; + private List _PublicKeys; + private UserConfig _UserConfig; + private ContactConfig _ContactConfig; + private EmailConfig _EmailConfig; + private GitConfig _GitConfig; + private UploadConfig _UploadConfig; + private PasteConfig _PasteConfig; + private BlogConfig _BlogConfig; + private ApiConfig _ApiConfig; + private PodcastConfig _PodcastConfig; + private StreamConfig _StreamConfig; + private ShortenerConfig _ShortenerConfig; + private VaultConfig _VaultConfig; + private StatsConfig _StatsConfig; + private LoggingConfig _LoggingConfig; + private PiwikConfig _PiwikConfig; + private IRCConfig _IRCConfig; public bool DevEnvironment { get { return _DevEnvironment; } set { _DevEnvironment = value; } } public bool Migrate { get { return _Migrate; } set { _Migrate = value; } } @@ -73,52 +73,52 @@ namespace Teknik.Configuration public List PublicKeys { get { return _PublicKeys; } set { _PublicKeys = value; } } // User Configuration - public UserConfig UserConfig { get { return _UserConfig; } set { _UserConfig = value; } } + public UserConfig UserConfig { get { return _UserConfig; } set { _UserConfig = value; } } // Contact Configuration - public ContactConfig ContactConfig { get { return _ContactConfig; } set { _ContactConfig = value; } } + public ContactConfig ContactConfig { get { return _ContactConfig; } set { _ContactConfig = value; } } // Mail Server Configuration - public EmailConfig EmailConfig { get { return _EmailConfig; } set { _EmailConfig = value; } } + public EmailConfig EmailConfig { get { return _EmailConfig; } set { _EmailConfig = value; } } // Git Service Configuration - public GitConfig GitConfig { get { return _GitConfig; } set { _GitConfig = value; } } + public GitConfig GitConfig { get { return _GitConfig; } set { _GitConfig = value; } } // Blog Configuration - public BlogConfig BlogConfig { get { return _BlogConfig; } set { _BlogConfig = value; } } + public BlogConfig BlogConfig { get { return _BlogConfig; } set { _BlogConfig = value; } } // Upload Configuration - public UploadConfig UploadConfig { get { return _UploadConfig; } set { _UploadConfig = value; } } + public UploadConfig UploadConfig { get { return _UploadConfig; } set { _UploadConfig = value; } } // Paste Configuration - public PasteConfig PasteConfig { get { return _PasteConfig; } set { _PasteConfig = value; } } + public PasteConfig PasteConfig { get { return _PasteConfig; } set { _PasteConfig = value; } } // API Configuration - public ApiConfig ApiConfig { get { return _ApiConfig; } set { _ApiConfig = value; } } + public ApiConfig ApiConfig { get { return _ApiConfig; } set { _ApiConfig = value; } } // Podcast Configuration - public PodcastConfig PodcastConfig { get { return _PodcastConfig; } set { _PodcastConfig = value; } } + public PodcastConfig PodcastConfig { get { return _PodcastConfig; } set { _PodcastConfig = value; } } // Stream Configuration - public StreamConfig StreamConfig { get { return _StreamConfig; } set { _StreamConfig = value; } } + public StreamConfig StreamConfig { get { return _StreamConfig; } set { _StreamConfig = value; } } // Shortener Configuration - public ShortenerConfig ShortenerConfig { get { return _ShortenerConfig; } set { _ShortenerConfig = value; } } + public ShortenerConfig ShortenerConfig { get { return _ShortenerConfig; } set { _ShortenerConfig = value; } } // Vault Configuration - public VaultConfig VaultConfig { get { return _VaultConfig; } set { _VaultConfig = value; } } + public VaultConfig VaultConfig { get { return _VaultConfig; } set { _VaultConfig = value; } } // Status Configuration - public StatsConfig StatsConfig { get { return _StatsConfig; } set { _StatsConfig = value; } } + public StatsConfig StatsConfig { get { return _StatsConfig; } set { _StatsConfig = value; } } // Logging Configuration - public LoggingConfig LoggingConfig { get { return _LoggingConfig; } set { _LoggingConfig = value; } } + public LoggingConfig LoggingConfig { get { return _LoggingConfig; } set { _LoggingConfig = value; } } // Piwik Configuration - public PiwikConfig PiwikConfig { get { return _PiwikConfig; } set { _PiwikConfig = value; } } + public PiwikConfig PiwikConfig { get { return _PiwikConfig; } set { _PiwikConfig = value; } } // Piwik Configuration - public IRCConfig IRCConfig { get { return _IRCConfig; } set { _IRCConfig = value; } } + public IRCConfig IRCConfig { get { return _IRCConfig; } set { _IRCConfig = value; } } public Config() { diff --git a/Configuration/Configuration.csproj b/Configuration/Configuration.csproj index 3dc7377..00253d7 100644 --- a/Configuration/Configuration.csproj +++ b/Configuration/Configuration.csproj @@ -5,6 +5,7 @@ Teknik.Configuration Teknik.Configuration win-x86;win-x64;linux-x64;linux-arm;osx-x64 + Debug;Release;Test diff --git a/Configuration/IdentityServerConfig.cs b/Configuration/IdentityServerConfig.cs new file mode 100644 index 0000000..b8dfb64 --- /dev/null +++ b/Configuration/IdentityServerConfig.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Teknik.Configuration +{ + public class IdentityServerConfig + { + public string Authority { get; set; } + + public string ClientId { get; set; } + public string ClientSecret { get; set; } + public List RedirectUris { get; set; } + public List PostLogoutRedirectUris { get; set; } + + public string APIName { get; set; } + public string APISecret { get; set; } + + public IdentityServerConfig() + { + Authority = "https://localhost:5002"; + ClientId = "mvc.client"; + ClientSecret = "mysecret"; + RedirectUris = new List(); + PostLogoutRedirectUris = new List(); + APIName = "api"; + APISecret = "secret"; + } + } +} diff --git a/Configuration/UserConfig.cs b/Configuration/UserConfig.cs index 8de0d1c..165cb87 100644 --- a/Configuration/UserConfig.cs +++ b/Configuration/UserConfig.cs @@ -13,6 +13,7 @@ namespace Teknik.Configuration public decimal PremiumAccountPrice { get; set; } public string PaymentType { get; set; } public bool InviteCodeRequired { get; set; } + public IdentityServerConfig IdentityServerConfig { get; set; } public UserConfig() { @@ -27,6 +28,7 @@ namespace Teknik.Configuration PremiumAccountPrice = 0; PaymentType = "Donation"; InviteCodeRequired = false; + IdentityServerConfig = new IdentityServerConfig(); } } } diff --git a/GitService/GitService.csproj b/GitService/GitService.csproj index d2c12e1..0e77ad3 100644 --- a/GitService/GitService.csproj +++ b/GitService/GitService.csproj @@ -8,7 +8,7 @@ - + diff --git a/IdentityServer/ApplicationDbContext.cs b/IdentityServer/ApplicationDbContext.cs new file mode 100644 index 0000000..4751fa6 --- /dev/null +++ b/IdentityServer/ApplicationDbContext.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Teknik.IdentityServer.Models; + +namespace Teknik.IdentityServer +{ + public class ApplicationDbContext : IdentityDbContext + { + public ApplicationDbContext(DbContextOptions options) : base(options) { } + } +} \ No newline at end of file diff --git a/IdentityServer/Configuration.cs b/IdentityServer/Configuration.cs new file mode 100644 index 0000000..2185969 --- /dev/null +++ b/IdentityServer/Configuration.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using IdentityModel; +using IdentityServer4; +using IdentityServer4.Models; +using IdentityServer4.Test; +using Teknik.Configuration; + +namespace Teknik.IdentityServer.Configuration +{ + internal class Clients + { + public static IEnumerable Get(Config config) + { + return new List { + new Client + { + ClientId = config.UserConfig.IdentityServerConfig.ClientId, + ClientName = "Teknik Web Services", + AllowedGrantTypes = GrantTypes.HybridAndClientCredentials, + + ClientSecrets = + { + new Secret(config.UserConfig.IdentityServerConfig.ClientSecret.Sha256()) + }, + + RequireConsent = false, + + AllowedScopes = + { + IdentityServerConstants.StandardScopes.OpenId, + "role", + "account-info", + "security-info", + "teknik-api.read", + "teknik-api.write", + "auth-api" + }, + AllowOfflineAccess = true + } + }; + } + } + + internal class Resources + { + public static IEnumerable GetIdentityResources() + { + return new List { + new IdentityResources.OpenId(), + new IdentityResource + { + Name = "account-info", + DisplayName = "Account Info", + UserClaims = new List + { + "username", + "email", + "creation-date", + "last-seen", + "account-type", + "account-status" + } + }, + new IdentityResource + { + Name = "security-info", + DisplayName = "Security Info", + UserClaims = new List + { + "recovery-email", + "recovery-verified", + "pgp-public-key" + } + }, + new IdentityResource { + Name = "role", + DisplayName = "Role", + UserClaims = new List {"role"} + } + }; + } + + public static IEnumerable GetApiResources(Config config) + { + return new List { + new ApiResource { + Name = config.UserConfig.IdentityServerConfig.APIName, + DisplayName = "Teknik API", + Description = "Teknik API Access for end users", + UserClaims = new List {"role"}, + ApiSecrets = new List {new Secret(config.UserConfig.IdentityServerConfig.APISecret.Sha256()) }, + Scopes = new List { + new Scope("teknik-api.read", "Teknik API Read Access"), + new Scope("teknik-api.write", "Teknik API Write Access") + } + }, + new ApiResource { + Name = "auth-api", + DisplayName = "Auth Server API", + Description = "Auth Server API Access for managing the Auth Server", + Scopes = new List { + new Scope() + { + Name = "auth-api", + ShowInDiscoveryDocument = false, + Required = true + } + } + } + }; + } + } + + internal class Policies + { + public static IEnumerable Get() + { + return new List + { + new Policy + { + Name = "Internal", + Scopes = { "auth-api" } + } + }; + } + } + + internal class Policy + { + public string Name { get; set; } + public ICollection Scopes { get; set; } + + public Policy() + { + Name = string.Empty; + Scopes = new List(); + } + } +} \ No newline at end of file diff --git a/IdentityServer/Content/common.css b/IdentityServer/Content/common.css new file mode 100644 index 0000000..b0f4beb --- /dev/null +++ b/IdentityServer/Content/common.css @@ -0,0 +1,85 @@ +body { + margin-top: 15px; +} +.navbar-header { + position: relative; + top: -4px; +} +.navbar-brand > .icon-banner { + position: relative; + top: -2px; + display: inline; +} +label { + font-weight: normal !important; +} +.icon { + position: relative; + top: -10px; +} +.logged-out iframe { + display: none; + width: 0; + height: 0; +} +.page-consent .client-logo { + float: left; +} +.page-consent .client-logo img { + width: 80px; + height: 80px; +} +.page-consent .consent-buttons { + margin-top: 25px; +} +.page-consent .consent-form .consent-scopecheck { + display: inline-block; + margin-right: 5px; +} +.page-consent .consent-form .consent-description { + margin-left: 25px; +} +.page-consent .consent-form .consent-description label { + font-weight: normal; +} +.page-consent .consent-form .consent-remember { + padding-left: 16px; +} +.grants .page-header { + margin-bottom: 10px; +} +.grants .grant { + margin-top: 20px; + padding-bottom: 20px; + border-bottom: 1px solid lightgray; +} +.grants .grant img { + width: 100px; + height: 100px; +} +.grants .grant .clientname { + font-size: 140%; + font-weight: bold; +} +.grants .grant .granttype { + font-size: 120%; + font-weight: bold; +} +.grants .grant .created { + font-size: 120%; + font-weight: bold; +} +.grants .grant .expires { + font-size: 120%; + font-weight: bold; +} +.grants .grant li { + list-style-type: none; + display: inline; +} +.grants .grant li:after { + content: ', '; +} +.grants .grant li:last-child:after { + content: ''; +} \ No newline at end of file diff --git a/IdentityServer/Controllers/AccountController.cs b/IdentityServer/Controllers/AccountController.cs new file mode 100644 index 0000000..ee078e9 --- /dev/null +++ b/IdentityServer/Controllers/AccountController.cs @@ -0,0 +1,307 @@ +using IdentityModel; +using IdentityServer4.Services; +using IdentityServer4.Stores; +using IdentityServer4.Test; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Security.Principal; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using IdentityServer4.Events; +using IdentityServer4.Extensions; +using IdentityServer4.Models; +using Microsoft.AspNetCore.Identity; +using Teknik.IdentityServer.Security; +using Teknik.IdentityServer.Services; +using Teknik.IdentityServer.ViewModels; +using Teknik.IdentityServer.Options; +using Teknik.IdentityServer.Models; +using Microsoft.Extensions.Logging; +using Teknik.Logging; +using Teknik.Configuration; + +namespace Teknik.IdentityServer.Controllers +{ + public class AccountController : DefaultController + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly IIdentityServerInteractionService _interaction; + private readonly IEventService _events; + private readonly AccountService _account; + + public AccountController( + ILogger logger, + Config config, + IIdentityServerInteractionService interaction, + IClientStore clientStore, + IHttpContextAccessor httpContextAccessor, + IAuthenticationSchemeProvider schemeProvider, + IEventService events, + UserManager userManager, + SignInManager signInManager) : base(logger, config) + { + // if the TestUserStore is not in DI, then we'll just use the global users collection + _userManager = userManager; + _signInManager = signInManager; + _interaction = interaction; + _events = events; + _account = new AccountService(interaction, httpContextAccessor, schemeProvider, clientStore); + } + + /// + /// Show login page + /// + [HttpGet] + public async Task Login(string returnUrl) + { + // build a model so we know what to show on the login page + var vm = await _account.BuildLoginViewModelAsync(returnUrl); + + return View(vm); + } + + /// + /// Handle postback from username/password login + /// + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Login(LoginViewModel model, string button, string returnUrl = null) + { + if (button != "login") + { + // the user clicked the "cancel" button + var context = await _interaction.GetAuthorizationContextAsync(returnUrl); + if (context != null) + { + // if the user cancels, send a result back into IdentityServer as if they + // denied the consent (even if this client does not require consent). + // this will send back an access denied OIDC error response to the client. + await _interaction.GrantConsentAsync(context, ConsentResponse.Denied); + + // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null + return Redirect(returnUrl); + } + else + { + // since we don't have a valid context, then we just go back to the home page + return Redirect("~/"); + } + } + + if (ModelState.IsValid) + { + // Check to see if the user is banned + var foundUser = await _userManager.FindByNameAsync(model.Username); + if (foundUser != null) + { + if (foundUser.AccountStatus == Utilities.AccountStatus.Banned) + { + // Redirect to banned page + return RedirectToAction(nameof(Banned)); + } + + var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberMe, false); + + if (result.Succeeded) + { + // make sure the returnUrl is still valid, and if so redirect back to authorize endpoint or a local page + if (_interaction.IsValidReturnUrl(returnUrl) || Url.IsLocalUrl(returnUrl)) + { + return Redirect(returnUrl); + } + + return Redirect("~/"); + } + if (result.RequiresTwoFactor) + { + // Redirect to 2FA page + return RedirectToAction(nameof(LoginWith2fa), new { returnUrl, model.RememberMe }); + } + if (result.IsLockedOut) + { + // Redirect to locked out page + return RedirectToAction(nameof(Lockout)); + } + } + + await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials")); + + ModelState.AddModelError("", AccountOptions.InvalidCredentialsErrorMessage); + } + + // something went wrong, show form with error + var vm = await _account.BuildLoginViewModelAsync(model); + return View(vm); + } + + [HttpGet] + public async Task LoginWith2fa(bool rememberMe, string returnUrl = null) + { + // Ensure the user has gone through the username & password screen first + var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); + + if (user == null) + { + throw new ApplicationException($"Unable to load two-factor authentication user."); + } + + var model = new LoginWith2faViewModel { RememberMe = rememberMe }; + ViewData["ReturnUrl"] = returnUrl; + + return View(model); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task LoginWith2fa(LoginWith2faViewModel model, bool rememberMe, string returnUrl = null) + { + if (!ModelState.IsValid) + { + return View(model); + } + + var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); + if (user == null) + { + throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + var authenticatorCode = model.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty); + + var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, model.RememberMachine); + + if (result.Succeeded) + { + return RedirectToLocal(returnUrl); + } + else if (result.IsLockedOut) + { + return RedirectToAction(nameof(Lockout)); + } + else + { + ModelState.AddModelError(string.Empty, "Invalid authenticator code."); + return View(); + } + } + + [HttpGet] + public async Task LoginWithRecoveryCode(string returnUrl = null) + { + // Ensure the user has gone through the username & password screen first + var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); + if (user == null) + { + throw new ApplicationException($"Unable to load two-factor authentication user."); + } + + ViewData["ReturnUrl"] = returnUrl; + + return View(); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task LoginWithRecoveryCode(LoginWithRecoveryCodeViewModel model, string returnUrl = null) + { + if (!ModelState.IsValid) + { + return View(model); + } + + var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); + if (user == null) + { + throw new ApplicationException($"Unable to load two-factor authentication user."); + } + + var recoveryCode = model.RecoveryCode.Replace(" ", string.Empty); + + var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode); + + if (result.Succeeded) + { + return RedirectToLocal(returnUrl); + } + if (result.IsLockedOut) + { + return RedirectToAction(nameof(Lockout)); + } + else + { + ModelState.AddModelError(string.Empty, "Invalid recovery code entered."); + return View(); + } + } + + [HttpGet] + public IActionResult Lockout() + { + return View(); + } + + [HttpGet] + public IActionResult Banned() + { + return View(); + } + + /// + /// Show logout page + /// + [HttpGet] + public async Task Logout(string logoutId) + { + // build a model so the logout page knows what to display + var vm = await _account.BuildLogoutViewModelAsync(logoutId); + + if (vm.ShowLogoutPrompt == false) + { + // if the request for logout was properly authenticated from IdentityServer, then + // we don't need to show the prompt and can just log the user out directly. + return await Logout(vm); + } + + return View(vm); + } + + /// + /// Handle logout page postback + /// + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Logout(LogoutInputModel model) + { + // get context information (client name, post logout redirect URI and iframe for federated signout) + var vm = await _account.BuildLoggedOutViewModelAsync(model.LogoutId); + + if (User?.Identity.IsAuthenticated == true) + { + await _signInManager.SignOutAsync(); + + // raise the logout event + await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName())); + } + + return View("LoggedOut", vm); + return RedirectToLocal(model.ReturnURL); + } + + private IActionResult RedirectToLocal(string returnUrl) + { + if (Url.IsLocalUrl(returnUrl)) + { + return Redirect(returnUrl); + } + else + { + return RedirectToAction(nameof(HomeController.Index), "Home"); + } + } + } +} \ No newline at end of file diff --git a/IdentityServer/Controllers/ConsentController.cs b/IdentityServer/Controllers/ConsentController.cs new file mode 100644 index 0000000..1e5e97d --- /dev/null +++ b/IdentityServer/Controllers/ConsentController.cs @@ -0,0 +1,75 @@ +using IdentityServer4.Services; +using IdentityServer4.Stores; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using System.Threading.Tasks; +using Teknik.Configuration; +using Teknik.IdentityServer.Models; +using Teknik.IdentityServer.Security; +using Teknik.IdentityServer.Services; +using Teknik.Logging; + +namespace Teknik.IdentityServer.Controllers +{ + /// + /// This controller processes the consent UI + /// + public class ConsentController : DefaultController + { + private readonly ConsentService _consent; + + public ConsentController( + ILogger logger, + Config config, + IIdentityServerInteractionService interaction, + IClientStore clientStore, + IResourceStore resourceStore) : base(logger, config) + { + _consent = new ConsentService(interaction, clientStore, resourceStore, logger); + } + + /// + /// Shows the consent screen + /// + /// + /// + [HttpGet] + public async Task Index(string returnUrl) + { + var vm = await _consent.BuildViewModelAsync(returnUrl); + if (vm != null) + { + return View("Index", vm); + } + + return View("Error"); + } + + /// + /// Handles the consent screen postback + /// + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Index(ConsentInputModel model) + { + var result = await _consent.ProcessConsent(model); + + if (result.IsRedirect) + { + return Redirect(result.RedirectUri); + } + + if (result.HasValidationError) + { + ModelState.AddModelError("", result.ValidationError); + } + + if (result.ShowView) + { + return View("Index", result.ViewModel); + } + + return View("Error"); + } + } +} \ No newline at end of file diff --git a/IdentityServer/Controllers/DefaultController.cs b/IdentityServer/Controllers/DefaultController.cs new file mode 100644 index 0000000..b7fc1b5 --- /dev/null +++ b/IdentityServer/Controllers/DefaultController.cs @@ -0,0 +1,64 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Teknik.Configuration; +using Teknik.Logging; +using Teknik.Utilities; + +namespace Teknik.IdentityServer.Controllers +{ + public class DefaultController : Controller + { + protected readonly ILogger _logger; + protected readonly Config _config; + + public DefaultController(ILogger logger, Config config) + { + _logger = logger; + _config = config; + + ViewBag.Title = "Teknik Authentication"; + ViewBag.Description = "Teknik Authentication Service"; + } + + // Get the Favicon + [HttpGet] + [AllowAnonymous] + [ResponseCache(Duration = 31536000, Location = ResponseCacheLocation.Any)] + public IActionResult Favicon([FromServices] IHostingEnvironment env) + { + string imageFile = FileHelper.MapPath(env, Constants.FAVICON_PATH); + FileStream fs = new FileStream(imageFile, FileMode.Open, FileAccess.Read); + return File(fs, "image/x-icon"); + } + + // Get the Robots.txt + [HttpGet] + [AllowAnonymous] + public IActionResult Robots([FromServices] IHostingEnvironment env) + { + //string file = FileHelper.MapPath(env, Constants.ROBOTS_PATH); + return File(Constants.ROBOTS_PATH, "text/plain"); + } + + protected IActionResult GenerateActionResult(object json) + { + return GenerateActionResult(json, View()); + } + + protected IActionResult GenerateActionResult(object json, IActionResult result) + { + if (Request.IsAjaxRequest()) + { + return Json(json); + } + return result; + } + } +} diff --git a/IdentityServer/Controllers/ErrorController.cs b/IdentityServer/Controllers/ErrorController.cs new file mode 100644 index 0000000..3ab39c6 --- /dev/null +++ b/IdentityServer/Controllers/ErrorController.cs @@ -0,0 +1,237 @@ +using IdentityServer4.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Diagnostics; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Mail; +using System.Text; +using System.Threading.Tasks; +using Teknik.Configuration; +using Teknik.IdentityServer.ViewModels; +using Teknik.Logging; +using Teknik.Utilities; + +namespace Teknik.IdentityServer.Controllers +{ + public class ErrorController : DefaultController + { + private readonly IIdentityServerInteractionService _interaction; + + public ErrorController(ILogger logger, Config config, IIdentityServerInteractionService interaction) : base(logger, config) + { + _interaction = interaction; + } + + public IActionResult HttpError(int statusCode) + { + switch (statusCode) + { + case 401: + return Http401(); + case 403: + return Http403(); + case 404: + return Http404(); + default: + return HttpGeneral(statusCode); + } + } + + public IActionResult HttpGeneral(int statusCode) + { + ViewBag.Title = statusCode + " - " + _config.Title; + + LogError(LogLevel.Error, "HTTP Error Code: " + statusCode); + + ErrorViewModel model = new ErrorViewModel(); + model.StatusCode = statusCode; + + return GenerateActionResult(CreateErrorObj("Http", statusCode, "Invalid HTTP Response"), View("~/Views/Error/HttpGeneral.cshtml", model)); + } + + [AllowAnonymous] + public IActionResult Http401() + { + Response.StatusCode = StatusCodes.Status401Unauthorized; + + ViewBag.Title = "401 - " + _config.Title; + ViewBag.Description = "Unauthorized"; + + LogError(LogLevel.Error, "Unauthorized"); + + ErrorViewModel model = new ErrorViewModel(); + model.StatusCode = StatusCodes.Status401Unauthorized; + + return GenerateActionResult(CreateErrorObj("Http", StatusCodes.Status401Unauthorized, "Unauthorized"), View("~/Views/Error/Http401.cshtml", model)); + } + + [AllowAnonymous] + public IActionResult Http403() + { + Response.StatusCode = StatusCodes.Status403Forbidden; + + ViewBag.Title = "403 - " + _config.Title; + ViewBag.Description = "Access Denied"; + + LogError(LogLevel.Error, "Access Denied"); + + ErrorViewModel model = new ErrorViewModel(); + model.StatusCode = StatusCodes.Status403Forbidden; + + return GenerateActionResult(CreateErrorObj("Http", StatusCodes.Status403Forbidden, "Access Denied"), View("~/Views/Error/Http403.cshtml", model)); + } + + [AllowAnonymous] + public IActionResult Http404() + { + Response.StatusCode = StatusCodes.Status404NotFound; + + ViewBag.Title = "404 - " + _config.Title; + ViewBag.Description = "Uh Oh, can't find it!"; + + LogError(LogLevel.Warning, "Page Not Found"); + + ErrorViewModel model = new ErrorViewModel(); + model.StatusCode = StatusCodes.Status404NotFound; + + return GenerateActionResult(CreateErrorObj("Http", StatusCodes.Status404NotFound, "Page Not Found"), View("~/Views/Error/Http404.cshtml", model)); + } + + [AllowAnonymous] + public IActionResult Http500(Exception exception) + { + if (HttpContext != null) + { + var ex = HttpContext.Features.Get(); + if (ex != null) + { + exception = ex.Error; + } + HttpContext.Session.Set("Exception", exception); + } + + Response.StatusCode = StatusCodes.Status500InternalServerError; + + ViewBag.Title = "500 - " + _config.Title; + ViewBag.Description = "Something Borked"; + + LogError(LogLevel.Error, "Server Error", exception); + + ErrorViewModel model = new ErrorViewModel(); + model.StatusCode = StatusCodes.Status500InternalServerError; + model.Exception = exception; + + return GenerateActionResult(CreateErrorObj("Http", StatusCodes.Status500InternalServerError, exception.Message), View("~/Views/Error/Http500.cshtml", model)); + } + + [AllowAnonymous] + public async Task IdentityError(string errorId) + { + var message = await _interaction.GetErrorContextAsync(errorId); + + Response.StatusCode = StatusCodes.Status500InternalServerError; + + ViewBag.Title = "Identity Error - " + _config.Title; + ViewBag.Description = "The Identity Service threw an error"; + + LogError(LogLevel.Error, "Identity Error: " + message.Error); + + IdentityErrorViewModel model = new IdentityErrorViewModel(); + model.Title = message.Error; + model.Description = message.ErrorDescription; + + return GenerateActionResult(CreateErrorObj("Http", StatusCodes.Status500InternalServerError, message.Error), View("~/Views/Error/IdentityError.cshtml", model)); + } + + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public IActionResult SubmitErrorReport(SubmitReportViewModel model) + { + try + { + string exceptionMsg = model.Exception; + + // Try to grab the actual exception that occured + Exception ex = HttpContext.Session.Get("Exception"); + if (ex != null) + { + exceptionMsg = string.Format(@" +Exception: {0} + +Source: {1} + +Stack Trace: + +{2} +", ex.GetFullMessage(true), ex.Source, ex.StackTrace); + } + + // Let's also email the message to support + SmtpClient client = new SmtpClient(); + client.Host = _config.ContactConfig.EmailAccount.Host; + client.Port = _config.ContactConfig.EmailAccount.Port; + client.EnableSsl = _config.ContactConfig.EmailAccount.SSL; + client.DeliveryMethod = SmtpDeliveryMethod.Network; + client.UseDefaultCredentials = true; + client.Credentials = new System.Net.NetworkCredential(_config.ContactConfig.EmailAccount.Username, _config.ContactConfig.EmailAccount.Password); + client.Timeout = 5000; + + MailMessage mail = new MailMessage(new MailAddress(_config.NoReplyEmail, _config.NoReplyEmail), new MailAddress(_config.SupportEmail, "Teknik Support")); + mail.Sender = new MailAddress(_config.ContactConfig.EmailAccount.EmailAddress); + mail.Subject = "[Exception] Application Exception Occured"; + mail.Body = @" +An exception has occured at: " + model.CurrentUrl + @" + +---------------------------------------- +User Message: + +" + model.Message + @" + +---------------------------------------- +" + exceptionMsg; + mail.BodyEncoding = UTF8Encoding.UTF8; + mail.DeliveryNotificationOptions = DeliveryNotificationOptions.Never; + + client.Send(mail); + } + catch (Exception ex) + { + return Json(new { error = "Error submitting report. Exception: " + ex.Message }); + } + + return Json(new { result = "true" }); + } + + private object CreateErrorObj(string type, int statusCode, string message) + { + return new { error = new { type = type, status = statusCode, message = message } }; + } + + private void LogError(LogLevel level, string message) + { + LogError(level, message, null); + } + + private void LogError(LogLevel level, string message, Exception exception) + { + if (Request != null) + { + message += " | Url: " + Request.GetDisplayUrl(); + + message += " | Referred Url: " + Request.Headers["Referer"].ToString(); + + message += " | Method: " + Request.Method; + + message += " | User Agent: " + Request.Headers["User-Agent"].ToString(); + } + + _logger.Log(level, message, exception); + } + } +} diff --git a/IdentityServer/Controllers/GrantsController.cs b/IdentityServer/Controllers/GrantsController.cs new file mode 100644 index 0000000..a6e16a3 --- /dev/null +++ b/IdentityServer/Controllers/GrantsController.cs @@ -0,0 +1,92 @@ +using IdentityServer4.Services; +using IdentityServer4.Stores; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Teknik.IdentityServer.Security; +using Teknik.IdentityServer.ViewModels; +using Teknik.Logging; +using Microsoft.Extensions.Logging; +using Teknik.Configuration; + +namespace Teknik.IdentityServer.Controllers +{ + /// + /// This sample controller allows a user to revoke grants given to clients + /// + [Authorize(AuthenticationSchemes = "Identity.Application")] + public class GrantsController : DefaultController + { + private readonly IIdentityServerInteractionService _interaction; + private readonly IClientStore _clients; + private readonly IResourceStore _resources; + + public GrantsController( + ILogger logger, + Config config, + IIdentityServerInteractionService interaction, + IClientStore clients, + IResourceStore resources) : base(logger, config) + { + _interaction = interaction; + _clients = clients; + _resources = resources; + } + + /// + /// Show list of grants + /// + [HttpGet] + public async Task Index() + { + return View("Index", await BuildViewModelAsync()); + } + + /// + /// Handle postback to revoke a client + /// + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Revoke(string clientId) + { + await _interaction.RevokeUserConsentAsync(clientId); + return RedirectToAction("Index"); + } + + private async Task BuildViewModelAsync() + { + var grants = await _interaction.GetAllUserConsentsAsync(); + + var list = new List(); + foreach(var grant in grants) + { + var client = await _clients.FindClientByIdAsync(grant.ClientId); + if (client != null) + { + var resources = await _resources.FindResourcesByScopeAsync(grant.Scopes); + + var item = new GrantViewModel() + { + ClientId = client.ClientId, + ClientName = client.ClientName ?? client.ClientId, + ClientLogoUrl = client.LogoUri, + ClientUrl = client.ClientUri, + Created = grant.CreationTime, + Expires = grant.Expiration, + IdentityGrantNames = resources.IdentityResources.Select(x => x.DisplayName ?? x.Name).ToArray(), + ApiGrantNames = resources.ApiResources.Select(x => x.DisplayName ?? x.Name).ToArray() + }; + + list.Add(item); + } + } + + return new GrantsViewModel + { + Grants = list + }; + } + } +} \ No newline at end of file diff --git a/IdentityServer/Controllers/HomeController.cs b/IdentityServer/Controllers/HomeController.cs new file mode 100644 index 0000000..c1d4d06 --- /dev/null +++ b/IdentityServer/Controllers/HomeController.cs @@ -0,0 +1,29 @@ +using IdentityServer4.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using System.Threading.Tasks; +using Teknik.Configuration; +using Teknik.IdentityServer.Security; +using Teknik.IdentityServer.ViewModels; +using Teknik.Logging; + +namespace Teknik.IdentityServer.Controllers +{ + public class HomeController : DefaultController + { + private readonly IIdentityServerInteractionService _interaction; + + public HomeController( + ILogger logger, + Config config, + IIdentityServerInteractionService interaction) : base(logger, config) + { + _interaction = interaction; + } + + public IActionResult Index() + { + return View(); + } + } +} \ No newline at end of file diff --git a/IdentityServer/Controllers/ManageController.cs b/IdentityServer/Controllers/ManageController.cs new file mode 100644 index 0000000..6aec756 --- /dev/null +++ b/IdentityServer/Controllers/ManageController.cs @@ -0,0 +1,420 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Teknik.Configuration; +using Teknik.IdentityServer.Models; +using Teknik.IdentityServer.Models.Manage; +using Teknik.Logging; + +namespace Teknik.IdentityServer.Controllers +{ + [Authorize(Policy = "Internal", AuthenticationSchemes = "Bearer")] + [Route("[controller]/[action]")] + [ApiController] + public class ManageController : DefaultController + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + + public ManageController( + ILogger logger, + Config config, + UserManager userManager, + SignInManager signInManager) : base(logger, config) + { + _userManager = userManager; + _signInManager = signInManager; + } + + [HttpPost] + public async Task CreateUser(NewUserModel model) + { + if (string.IsNullOrEmpty(model.Username)) + return new JsonResult(new { success = false, message = "Username is required" }); + if (string.IsNullOrEmpty(model.Password)) + return new JsonResult(new { success = false, message = "Password is required" }); + + var identityUser = new ApplicationUser(model.Username) + { + Id = Guid.NewGuid().ToString(), + UserName = model.Username, + AccountStatus = model.AccountStatus, + AccountType = model.AccountType, + Email = model.RecoveryEmail, + EmailConfirmed = model.RecoveryVerified, + PGPPublicKey = model.PGPPublicKey + }; + var result = await _userManager.CreateAsync(identityUser, model.Password); + if (result.Succeeded) + { + return new JsonResult(new { success = true }); + } + + return new JsonResult(new { success = false, message = "Unable to create user.", identityErrors = result.Errors }); + } + + [HttpPost] + public async Task DeleteUser(DeleteUserModel model) + { + if (string.IsNullOrEmpty(model.Username)) + return new JsonResult(new { success = false, message = "Username is required" }); + + var foundUser = await _userManager.FindByNameAsync(model.Username); + if (foundUser != null) + { + var result = await _userManager.DeleteAsync(foundUser); + if (result.Succeeded) + return new JsonResult(new { success = true }); + else + return new JsonResult(new { success = false, message = "Unable to delete user.", identityErrors = result.Errors }); + } + + return new JsonResult(new { success = false, message = "User does not exist." }); + } + + [HttpGet] + public async Task UserExists(string username) + { + if (string.IsNullOrEmpty(username)) + return new JsonResult(new { success = false, message = "Username is required" }); + + var foundUser = await _userManager.FindByNameAsync(username); + return new JsonResult(new { success = true, data = foundUser != null }); + } + + [HttpGet] + public async Task GetUserInfo(string username) + { + if (string.IsNullOrEmpty(username)) + return new JsonResult(new { success = false, message = "Username is required" }); + + var foundUser = await _userManager.FindByNameAsync(username); + if (foundUser != null) + { + return new JsonResult(new { success = true, data = foundUser.ToJson() }); + } + + return new JsonResult(new { success = false, message = "User does not exist." }); + } + + [HttpPost] + public async Task CheckPassword(CheckPasswordModel model) + { + if (string.IsNullOrEmpty(model.Username)) + return new JsonResult(new { success = false, message = "Username is required" }); + if (string.IsNullOrEmpty(model.Password)) + return new JsonResult(new { success = false, message = "Password is required" }); + + var foundUser = await _userManager.FindByNameAsync(model.Username); + if (foundUser != null) + { + bool valid = await _userManager.CheckPasswordAsync(foundUser, model.Password); + return new JsonResult(new { success = true, data = valid }); + } + + return new JsonResult(new { success = false, message = "User does not exist." }); + } + + [HttpPost] + public async Task GeneratePasswordResetToken(GeneratePasswordResetTokenModel model) + { + if (string.IsNullOrEmpty(model.Username)) + return new JsonResult(new { success = false, message = "Username is required" }); + + var foundUser = await _userManager.FindByNameAsync(model.Username); + if (foundUser != null) + { + string token = await _userManager.GeneratePasswordResetTokenAsync(foundUser); + return new JsonResult(new { success = true, data = token }); + } + + return new JsonResult(new { success = false, message = "User does not exist." }); + } + + [HttpPost] + public async Task ResetPassword(ResetPasswordModel model) + { + if (string.IsNullOrEmpty(model.Username)) + return new JsonResult(new { success = false, message = "Username is required" }); + if (string.IsNullOrEmpty(model.Token)) + return new JsonResult(new { success = false, message = "Token is required" }); + if (string.IsNullOrEmpty(model.Password)) + return new JsonResult(new { success = false, message = "Password is required" }); + + var foundUser = await _userManager.FindByNameAsync(model.Username); + if (foundUser != null) + { + var result = await _userManager.ResetPasswordAsync(foundUser, model.Token, model.Password); + if (result.Succeeded) + return new JsonResult(new { success = true }); + else + return new JsonResult(new { success = false, message = "Unable to reset password.", identityErrors = result.Errors }); + } + + return new JsonResult(new { success = false, message = "User does not exist." }); + } + + [HttpPost] + public async Task UpdatePassword(UpdatePasswordModel model) + { + if (string.IsNullOrEmpty(model.Username)) + return new JsonResult(new { success = false, message = "Username is required" }); + if (string.IsNullOrEmpty(model.CurrentPassword)) + return new JsonResult(new { success = false, message = "Current Password is required" }); + if (string.IsNullOrEmpty(model.NewPassword)) + return new JsonResult(new { success = false, message = "New Password is required" }); + + var foundUser = await _userManager.FindByNameAsync(model.Username); + if (foundUser != null) + { + var result = await _userManager.ChangePasswordAsync(foundUser, model.CurrentPassword, model.NewPassword); + if (result.Succeeded) + return new JsonResult(new { success = true }); + else + return new JsonResult(new { success = false, message = "Unable to update password.", identityErrors = result.Errors }); + } + + return new JsonResult(new { success = false, message = "User does not exist." }); + } + + [HttpPost] + public async Task UpdateEmail(UpdateEmailModel model) + { + if (string.IsNullOrEmpty(model.Username)) + return new JsonResult(new { success = false, message = "Username is required" }); + + var foundUser = await _userManager.FindByNameAsync(model.Username); + if (foundUser != null) + { + var result = await _userManager.SetEmailAsync(foundUser, model.Email); + if (result.Succeeded) + { + var token = await _userManager.GenerateEmailConfirmationTokenAsync(foundUser); + return new JsonResult(new { success = true, data = token }); + } + else + return new JsonResult(new { success = false, message = "Unable to update email address.", identityErrors = result.Errors }); + } + + return new JsonResult(new { success = false, message = "User does not exist." }); + } + + [HttpPost] + public async Task VerifyEmail(VerifyEmailModel model) + { + if (string.IsNullOrEmpty(model.Username)) + return new JsonResult(new { success = false, message = "Username is required" }); + if (string.IsNullOrEmpty(model.Token)) + return new JsonResult(new { success = false, message = "Token is required" }); + + var foundUser = await _userManager.FindByNameAsync(model.Username); + if (foundUser != null) + { + var result = await _userManager.ConfirmEmailAsync(foundUser, model.Token); + if (result.Succeeded) + return new JsonResult(new { success = true }); + else + return new JsonResult(new { success = false, message = "Unable to verify email address.", identityErrors = result.Errors }); + } + + return new JsonResult(new { success = false, message = "User does not exist." }); + } + + [HttpPost] + public async Task UpdateAccountStatus(UpdateAccountStatusModel model) + { + if (string.IsNullOrEmpty(model.Username)) + return new JsonResult(new { success = false, message = "Username is required" }); + + var foundUser = await _userManager.FindByNameAsync(model.Username); + if (foundUser != null) + { + foundUser.AccountStatus = model.AccountStatus; + + var result = await _userManager.UpdateAsync(foundUser); + if (result.Succeeded) + return new JsonResult(new { success = true }); + else + return new JsonResult(new { success = false, message = "Unable to update account status.", identityErrors = result.Errors }); + } + + return new JsonResult(new { success = false, message = "User does not exist." }); + } + + [HttpPost] + public async Task UpdateAccountType(UpdateAccountTypeModel model) + { + if (string.IsNullOrEmpty(model.Username)) + return new JsonResult(new { success = false, message = "Username is required" }); + + var foundUser = await _userManager.FindByNameAsync(model.Username); + if (foundUser != null) + { + foundUser.AccountType = model.AccountType; + + var result = await _userManager.UpdateAsync(foundUser); + if (result.Succeeded) + return new JsonResult(new { success = true }); + else + return new JsonResult(new { success = false, message = "Unable to update account type.", identityErrors = result.Errors }); + } + + return new JsonResult(new { success = false, message = "User does not exist." }); + } + + [HttpPost] + public async Task UpdatePGPPublicKey(UpdatePGPPublicKeyModel model) + { + if (string.IsNullOrEmpty(model.Username)) + return new JsonResult(new { success = false, message = "Username is required" }); + + var foundUser = await _userManager.FindByNameAsync(model.Username); + if (foundUser != null) + { + foundUser.PGPPublicKey = model.PGPPublicKey; + + var result = await _userManager.UpdateAsync(foundUser); + if (result.Succeeded) + return new JsonResult(new { success = true }); + else + return new JsonResult(new { success = false, message = "Unable to update pgp public key.", identityErrors = result.Errors }); + } + + return new JsonResult(new { success = false, message = "User does not exist." }); + } + + [HttpGet] + public async Task Get2FAKey(string username) + { + if (string.IsNullOrEmpty(username)) + return new JsonResult(new { success = false, message = "Username is required" }); + + var foundUser = await _userManager.FindByNameAsync(username); + if (foundUser != null) + { + string unformattedKey = await _userManager.GetAuthenticatorKeyAsync(foundUser); + + return new JsonResult(new { success = true, data = FormatKey(unformattedKey) }); + } + + return new JsonResult(new { success = false, message = "User does not exist." }); + } + + [HttpPost] + public async Task Reset2FAKey(Reset2FAKeyModel model) + { + if (string.IsNullOrEmpty(model.Username)) + return new JsonResult(new { success = false, message = "Username is required" }); + + var foundUser = await _userManager.FindByNameAsync(model.Username); + if (foundUser != null) + { + await _userManager.ResetAuthenticatorKeyAsync(foundUser); + string unformattedKey = await _userManager.GetAuthenticatorKeyAsync(foundUser); + + return new JsonResult(new { success = true, data = FormatKey(unformattedKey) }); + } + + return new JsonResult(new { success = false, message = "User does not exist." }); + } + + [HttpPost] + public async Task Enable2FA(Enable2FAModel model) + { + if (string.IsNullOrEmpty(model.Username)) + return new JsonResult(new { success = false, message = "Username is required" }); + if (string.IsNullOrEmpty(model.Code)) + return new JsonResult(new { success = false, message = "Code is required" }); + + var foundUser = await _userManager.FindByNameAsync(model.Username); + if (foundUser != null) + { + // Strip spaces and hypens + var verificationCode = model.Code.Replace(" ", string.Empty).Replace("-", string.Empty); + + var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync( + foundUser, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode); + + if (is2faTokenValid) + { + var result = await _userManager.SetTwoFactorEnabledAsync(foundUser, true); + if (result.Succeeded) + { + var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(foundUser, 10); + return new JsonResult(new { success = true, data = recoveryCodes.ToArray() }); + } + else + return new JsonResult(new { success = false, message = "Unable to set Two-Factor Authentication.", identityErrors = result.Errors }); + } + + return new JsonResult(new { success = false, message = "Verification code is invalid." }); + } + + return new JsonResult(new { success = false, message = "User does not exist." }); + } + + [HttpPost] + public async Task Disable2FA(Disable2FAModel model) + { + if (string.IsNullOrEmpty(model.Username)) + return new JsonResult(new { success = false, message = "Username is required" }); + + var foundUser = await _userManager.FindByNameAsync(model.Username); + if (foundUser != null) + { + var result = await _userManager.SetTwoFactorEnabledAsync(foundUser, false); + if (result.Succeeded) + return new JsonResult(new { success = true }); + else + return new JsonResult(new { success = false, message = "Unable to disable Two-Factor Authentication.", identityErrors = result.Errors }); + } + + return new JsonResult(new { success = false, message = "User does not exist." }); + } + + [HttpPost] + public async Task GenerateRecoveryCodes(GenerateRecoveryCodesModel model) + { + if (string.IsNullOrEmpty(model.Username)) + return new JsonResult(new { success = false, message = "Username is required" }); + + var foundUser = await _userManager.FindByNameAsync(model.Username); + if (foundUser != null) + { + if (foundUser.TwoFactorEnabled) + { + var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(foundUser, 10); + + return new JsonResult(new { success = true, data = recoveryCodes.ToArray() }); + } + + return new JsonResult(new { success = false, message = "Two-Factor Authentication is not enabled." }); + } + + return new JsonResult(new { success = false, message = "User does not exist." }); + } + + private string FormatKey(string unformattedKey) + { + var result = new StringBuilder(); + int currentPosition = 0; + while (currentPosition + 4 < unformattedKey.Length) + { + result.Append(unformattedKey.Substring(currentPosition, 4)).Append(" "); + currentPosition += 4; + } + if (currentPosition < unformattedKey.Length) + { + result.Append(unformattedKey.Substring(currentPosition)); + } + + return result.ToString().ToLowerInvariant(); + } + } +} \ No newline at end of file diff --git a/IdentityServer/Data/Migrations/ApplicationDb/20181015060219_InitialApplicationDbContextMigration.Designer.cs b/IdentityServer/Data/Migrations/ApplicationDb/20181015060219_InitialApplicationDbContextMigration.Designer.cs new file mode 100644 index 0000000..025287b --- /dev/null +++ b/IdentityServer/Data/Migrations/ApplicationDb/20181015060219_InitialApplicationDbContextMigration.Designer.cs @@ -0,0 +1,242 @@ +// +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("20181015060219_InitialApplicationDbContextMigration")] + partial class InitialApplicationDbContextMigration + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.0-preview2-35157") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Teknik.IdentityServer.Models.ApplicationUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("AccountStatus"); + + b.Property("AccountType"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("CreationDate"); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("LastSeen"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PGPPublicKey"); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Teknik.IdentityServer.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Teknik.IdentityServer.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Teknik.IdentityServer.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Teknik.IdentityServer.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/IdentityServer/Data/Migrations/ApplicationDb/20181015060219_InitialApplicationDbContextMigration.cs b/IdentityServer/Data/Migrations/ApplicationDb/20181015060219_InitialApplicationDbContextMigration.cs new file mode 100644 index 0000000..6a644c8 --- /dev/null +++ b/IdentityServer/Data/Migrations/ApplicationDb/20181015060219_InitialApplicationDbContextMigration.cs @@ -0,0 +1,225 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Teknik.IdentityServer.Data.Migrations.ApplicationDb +{ + public partial class InitialApplicationDbContextMigration : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(nullable: false), + Name = table.Column(maxLength: 256, nullable: true), + NormalizedName = table.Column(maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(nullable: false), + UserName = table.Column(maxLength: 256, nullable: true), + NormalizedUserName = table.Column(maxLength: 256, nullable: true), + Email = table.Column(maxLength: 256, nullable: true), + NormalizedEmail = table.Column(maxLength: 256, nullable: true), + EmailConfirmed = table.Column(nullable: false), + PasswordHash = table.Column(nullable: true), + SecurityStamp = table.Column(nullable: true), + ConcurrencyStamp = table.Column(nullable: true), + PhoneNumber = table.Column(nullable: true), + PhoneNumberConfirmed = table.Column(nullable: false), + TwoFactorEnabled = table.Column(nullable: false), + LockoutEnd = table.Column(nullable: true), + LockoutEnabled = table.Column(nullable: false), + AccessFailedCount = table.Column(nullable: false), + CreationDate = table.Column(nullable: false), + LastSeen = table.Column(nullable: false), + AccountType = table.Column(nullable: false), + AccountStatus = table.Column(nullable: false), + PGPPublicKey = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + RoleId = table.Column(nullable: false), + ClaimType = table.Column(nullable: true), + ClaimValue = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + UserId = table.Column(nullable: false), + ClaimType = table.Column(nullable: true), + ClaimValue = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(nullable: false), + ProviderKey = table.Column(nullable: false), + ProviderDisplayName = table.Column(nullable: true), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(nullable: false), + RoleId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(nullable: false), + LoginProvider = table.Column(nullable: false), + Name = table.Column(nullable: false), + Value = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/IdentityServer/Data/Migrations/ApplicationDb/ApplicationDbContextModelSnapshot.cs b/IdentityServer/Data/Migrations/ApplicationDb/ApplicationDbContextModelSnapshot.cs new file mode 100644 index 0000000..379c004 --- /dev/null +++ b/IdentityServer/Data/Migrations/ApplicationDb/ApplicationDbContextModelSnapshot.cs @@ -0,0 +1,240 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Teknik.IdentityServer; + +namespace Teknik.IdentityServer.Data.Migrations.ApplicationDb +{ + [DbContext(typeof(ApplicationDbContext))] + partial class ApplicationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.0-preview2-35157") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Teknik.IdentityServer.Models.ApplicationUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("AccountStatus"); + + b.Property("AccountType"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("CreationDate"); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("LastSeen"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PGPPublicKey"); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Teknik.IdentityServer.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Teknik.IdentityServer.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Teknik.IdentityServer.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Teknik.IdentityServer.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/IdentityServer/Data/Migrations/IdentityServer/ConfigurationDb/20180930040544_InitialIdentityServerConfigurationDbMigration.Designer.cs b/IdentityServer/Data/Migrations/IdentityServer/ConfigurationDb/20180930040544_InitialIdentityServerConfigurationDbMigration.Designer.cs new file mode 100644 index 0000000..2a694a4 --- /dev/null +++ b/IdentityServer/Data/Migrations/IdentityServer/ConfigurationDb/20180930040544_InitialIdentityServerConfigurationDbMigration.Designer.cs @@ -0,0 +1,679 @@ +// +using System; +using IdentityServer4.EntityFramework.DbContexts; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Teknik.IdentityServer.Data.Migrations.IdentityServer.ConfigurationDb +{ + [DbContext(typeof(ConfigurationDbContext))] + [Migration("20180930040544_InitialIdentityServerConfigurationDbMigration")] + partial class InitialIdentityServerConfigurationDbMigration + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.0-preview2-35157") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Created"); + + b.Property("Description") + .HasMaxLength(1000); + + b.Property("DisplayName") + .HasMaxLength(200); + + b.Property("Enabled"); + + b.Property("LastAccessed"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200); + + b.Property("Updated"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ApiResources"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ApiResourceId"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(200); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ApiResourceId"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2000); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiProperties"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ApiResourceId"); + + b.Property("Description") + .HasMaxLength(1000); + + b.Property("DisplayName") + .HasMaxLength(200); + + b.Property("Emphasize"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200); + + b.Property("Required"); + + b.Property("ShowInDiscoveryDocument"); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ApiScopes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ApiScopeId"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(200); + + b.HasKey("Id"); + + b.HasIndex("ApiScopeId"); + + b.ToTable("ApiScopeClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ApiResourceId"); + + b.Property("Created"); + + b.Property("Description") + .HasMaxLength(1000); + + b.Property("Expiration"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250); + + b.Property("Value") + .IsRequired() + .HasMaxLength(4000); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiSecrets"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AbsoluteRefreshTokenLifetime"); + + b.Property("AccessTokenLifetime"); + + b.Property("AccessTokenType"); + + b.Property("AllowAccessTokensViaBrowser"); + + b.Property("AllowOfflineAccess"); + + b.Property("AllowPlainTextPkce"); + + b.Property("AllowRememberConsent"); + + b.Property("AlwaysIncludeUserClaimsInIdToken"); + + b.Property("AlwaysSendClientClaims"); + + b.Property("AuthorizationCodeLifetime"); + + b.Property("BackChannelLogoutSessionRequired"); + + b.Property("BackChannelLogoutUri") + .HasMaxLength(2000); + + b.Property("ClientClaimsPrefix") + .HasMaxLength(200); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200); + + b.Property("ClientName") + .HasMaxLength(200); + + b.Property("ClientUri") + .HasMaxLength(2000); + + b.Property("ConsentLifetime"); + + b.Property("Created"); + + b.Property("Description") + .HasMaxLength(1000); + + b.Property("EnableLocalLogin"); + + b.Property("Enabled"); + + b.Property("FrontChannelLogoutSessionRequired"); + + b.Property("FrontChannelLogoutUri") + .HasMaxLength(2000); + + b.Property("IdentityTokenLifetime"); + + b.Property("IncludeJwtId"); + + b.Property("LastAccessed"); + + b.Property("LogoUri") + .HasMaxLength(2000); + + b.Property("PairWiseSubjectSalt") + .HasMaxLength(200); + + b.Property("ProtocolType") + .IsRequired() + .HasMaxLength(200); + + b.Property("RefreshTokenExpiration"); + + b.Property("RefreshTokenUsage"); + + b.Property("RequireClientSecret"); + + b.Property("RequireConsent"); + + b.Property("RequirePkce"); + + b.Property("SlidingRefreshTokenLifetime"); + + b.Property("UpdateAccessTokenClaimsOnRefresh"); + + b.Property("Updated"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.ToTable("Clients"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClientId"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250); + + b.Property("Value") + .IsRequired() + .HasMaxLength(250); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClientId"); + + b.Property("Origin") + .IsRequired() + .HasMaxLength(150); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientCorsOrigins"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClientId"); + + b.Property("GrantType") + .IsRequired() + .HasMaxLength(250); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientGrantTypes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClientId"); + + b.Property("Provider") + .IsRequired() + .HasMaxLength(200); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientIdPRestrictions"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClientId"); + + b.Property("PostLogoutRedirectUri") + .IsRequired() + .HasMaxLength(2000); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientPostLogoutRedirectUris"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClientId"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2000); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientProperties"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClientId"); + + b.Property("RedirectUri") + .IsRequired() + .HasMaxLength(2000); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientRedirectUris"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClientId"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(200); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientScopes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClientId"); + + b.Property("Created"); + + b.Property("Description") + .HasMaxLength(2000); + + b.Property("Expiration"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250); + + b.Property("Value") + .IsRequired() + .HasMaxLength(4000); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientSecrets"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("IdentityResourceId"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(200); + + b.HasKey("Id"); + + b.HasIndex("IdentityResourceId"); + + b.ToTable("IdentityClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Created"); + + b.Property("Description") + .HasMaxLength(1000); + + b.Property("DisplayName") + .HasMaxLength(200); + + b.Property("Emphasize"); + + b.Property("Enabled"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200); + + b.Property("Required"); + + b.Property("ShowInDiscoveryDocument"); + + b.Property("Updated"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("IdentityResources"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("IdentityResourceId"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2000); + + b.HasKey("Id"); + + b.HasIndex("IdentityResourceId"); + + b.ToTable("IdentityProperties"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("UserClaims") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("Properties") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("Scopes") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "ApiScope") + .WithMany("UserClaims") + .HasForeignKey("ApiScopeId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiSecret", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("Secrets") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("Claims") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("AllowedCorsOrigins") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("AllowedGrantTypes") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("IdentityProviderRestrictions") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("PostLogoutRedirectUris") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("Properties") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("RedirectUris") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("AllowedScopes") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("ClientSecrets") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") + .WithMany("UserClaims") + .HasForeignKey("IdentityResourceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") + .WithMany("Properties") + .HasForeignKey("IdentityResourceId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/IdentityServer/Data/Migrations/IdentityServer/ConfigurationDb/20180930040544_InitialIdentityServerConfigurationDbMigration.cs b/IdentityServer/Data/Migrations/IdentityServer/ConfigurationDb/20180930040544_InitialIdentityServerConfigurationDbMigration.cs new file mode 100644 index 0000000..f669054 --- /dev/null +++ b/IdentityServer/Data/Migrations/IdentityServer/ConfigurationDb/20180930040544_InitialIdentityServerConfigurationDbMigration.cs @@ -0,0 +1,602 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Teknik.IdentityServer.Data.Migrations.IdentityServer.ConfigurationDb +{ + public partial class InitialIdentityServerConfigurationDbMigration : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ApiResources", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Enabled = table.Column(nullable: false), + Name = table.Column(maxLength: 200, nullable: false), + DisplayName = table.Column(maxLength: 200, nullable: true), + Description = table.Column(maxLength: 1000, nullable: true), + Created = table.Column(nullable: false), + Updated = table.Column(nullable: true), + LastAccessed = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiResources", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Clients", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Enabled = table.Column(nullable: false), + ClientId = table.Column(maxLength: 200, nullable: false), + ProtocolType = table.Column(maxLength: 200, nullable: false), + RequireClientSecret = table.Column(nullable: false), + ClientName = table.Column(maxLength: 200, nullable: true), + Description = table.Column(maxLength: 1000, nullable: true), + ClientUri = table.Column(maxLength: 2000, nullable: true), + LogoUri = table.Column(maxLength: 2000, nullable: true), + RequireConsent = table.Column(nullable: false), + AllowRememberConsent = table.Column(nullable: false), + AlwaysIncludeUserClaimsInIdToken = table.Column(nullable: false), + RequirePkce = table.Column(nullable: false), + AllowPlainTextPkce = table.Column(nullable: false), + AllowAccessTokensViaBrowser = table.Column(nullable: false), + FrontChannelLogoutUri = table.Column(maxLength: 2000, nullable: true), + FrontChannelLogoutSessionRequired = table.Column(nullable: false), + BackChannelLogoutUri = table.Column(maxLength: 2000, nullable: true), + BackChannelLogoutSessionRequired = table.Column(nullable: false), + AllowOfflineAccess = table.Column(nullable: false), + IdentityTokenLifetime = table.Column(nullable: false), + AccessTokenLifetime = table.Column(nullable: false), + AuthorizationCodeLifetime = table.Column(nullable: false), + ConsentLifetime = table.Column(nullable: true), + AbsoluteRefreshTokenLifetime = table.Column(nullable: false), + SlidingRefreshTokenLifetime = table.Column(nullable: false), + RefreshTokenUsage = table.Column(nullable: false), + UpdateAccessTokenClaimsOnRefresh = table.Column(nullable: false), + RefreshTokenExpiration = table.Column(nullable: false), + AccessTokenType = table.Column(nullable: false), + EnableLocalLogin = table.Column(nullable: false), + IncludeJwtId = table.Column(nullable: false), + AlwaysSendClientClaims = table.Column(nullable: false), + ClientClaimsPrefix = table.Column(maxLength: 200, nullable: true), + PairWiseSubjectSalt = table.Column(maxLength: 200, nullable: true), + Created = table.Column(nullable: false), + Updated = table.Column(nullable: true), + LastAccessed = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Clients", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "IdentityResources", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Enabled = table.Column(nullable: false), + Name = table.Column(maxLength: 200, nullable: false), + DisplayName = table.Column(maxLength: 200, nullable: true), + Description = table.Column(maxLength: 1000, nullable: true), + Required = table.Column(nullable: false), + Emphasize = table.Column(nullable: false), + ShowInDiscoveryDocument = table.Column(nullable: false), + Created = table.Column(nullable: false), + Updated = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityResources", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ApiClaims", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Type = table.Column(maxLength: 200, nullable: false), + ApiResourceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiClaims", x => x.Id); + table.ForeignKey( + name: "FK_ApiClaims_ApiResources_ApiResourceId", + column: x => x.ApiResourceId, + principalTable: "ApiResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ApiProperties", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Key = table.Column(maxLength: 250, nullable: false), + Value = table.Column(maxLength: 2000, nullable: false), + ApiResourceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiProperties", x => x.Id); + table.ForeignKey( + name: "FK_ApiProperties_ApiResources_ApiResourceId", + column: x => x.ApiResourceId, + principalTable: "ApiResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ApiScopes", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Name = table.Column(maxLength: 200, nullable: false), + DisplayName = table.Column(maxLength: 200, nullable: true), + Description = table.Column(maxLength: 1000, nullable: true), + Required = table.Column(nullable: false), + Emphasize = table.Column(nullable: false), + ShowInDiscoveryDocument = table.Column(nullable: false), + ApiResourceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiScopes", x => x.Id); + table.ForeignKey( + name: "FK_ApiScopes_ApiResources_ApiResourceId", + column: x => x.ApiResourceId, + principalTable: "ApiResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ApiSecrets", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Description = table.Column(maxLength: 1000, nullable: true), + Value = table.Column(maxLength: 4000, nullable: false), + Expiration = table.Column(nullable: true), + Type = table.Column(maxLength: 250, nullable: false), + Created = table.Column(nullable: false), + ApiResourceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiSecrets", x => x.Id); + table.ForeignKey( + name: "FK_ApiSecrets_ApiResources_ApiResourceId", + column: x => x.ApiResourceId, + principalTable: "ApiResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientClaims", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Type = table.Column(maxLength: 250, nullable: false), + Value = table.Column(maxLength: 250, nullable: false), + ClientId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientClaims", x => x.Id); + table.ForeignKey( + name: "FK_ClientClaims_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientCorsOrigins", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Origin = table.Column(maxLength: 150, nullable: false), + ClientId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientCorsOrigins", x => x.Id); + table.ForeignKey( + name: "FK_ClientCorsOrigins_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientGrantTypes", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + GrantType = table.Column(maxLength: 250, nullable: false), + ClientId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientGrantTypes", x => x.Id); + table.ForeignKey( + name: "FK_ClientGrantTypes_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientIdPRestrictions", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Provider = table.Column(maxLength: 200, nullable: false), + ClientId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientIdPRestrictions", x => x.Id); + table.ForeignKey( + name: "FK_ClientIdPRestrictions_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientPostLogoutRedirectUris", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + PostLogoutRedirectUri = table.Column(maxLength: 2000, nullable: false), + ClientId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientPostLogoutRedirectUris", x => x.Id); + table.ForeignKey( + name: "FK_ClientPostLogoutRedirectUris_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientProperties", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Key = table.Column(maxLength: 250, nullable: false), + Value = table.Column(maxLength: 2000, nullable: false), + ClientId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientProperties", x => x.Id); + table.ForeignKey( + name: "FK_ClientProperties_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientRedirectUris", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + RedirectUri = table.Column(maxLength: 2000, nullable: false), + ClientId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientRedirectUris", x => x.Id); + table.ForeignKey( + name: "FK_ClientRedirectUris_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientScopes", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Scope = table.Column(maxLength: 200, nullable: false), + ClientId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientScopes", x => x.Id); + table.ForeignKey( + name: "FK_ClientScopes_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientSecrets", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Description = table.Column(maxLength: 2000, nullable: true), + Value = table.Column(maxLength: 4000, nullable: false), + Expiration = table.Column(nullable: true), + Type = table.Column(maxLength: 250, nullable: false), + Created = table.Column(nullable: false), + ClientId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientSecrets", x => x.Id); + table.ForeignKey( + name: "FK_ClientSecrets_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IdentityClaims", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Type = table.Column(maxLength: 200, nullable: false), + IdentityResourceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityClaims", x => x.Id); + table.ForeignKey( + name: "FK_IdentityClaims_IdentityResources_IdentityResourceId", + column: x => x.IdentityResourceId, + principalTable: "IdentityResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IdentityProperties", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Key = table.Column(maxLength: 250, nullable: false), + Value = table.Column(maxLength: 2000, nullable: false), + IdentityResourceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityProperties", x => x.Id); + table.ForeignKey( + name: "FK_IdentityProperties_IdentityResources_IdentityResourceId", + column: x => x.IdentityResourceId, + principalTable: "IdentityResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ApiScopeClaims", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Type = table.Column(maxLength: 200, nullable: false), + ApiScopeId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiScopeClaims", x => x.Id); + table.ForeignKey( + name: "FK_ApiScopeClaims_ApiScopes_ApiScopeId", + column: x => x.ApiScopeId, + principalTable: "ApiScopes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ApiClaims_ApiResourceId", + table: "ApiClaims", + column: "ApiResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiProperties_ApiResourceId", + table: "ApiProperties", + column: "ApiResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiResources_Name", + table: "ApiResources", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ApiScopeClaims_ApiScopeId", + table: "ApiScopeClaims", + column: "ApiScopeId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiScopes_ApiResourceId", + table: "ApiScopes", + column: "ApiResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiScopes_Name", + table: "ApiScopes", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ApiSecrets_ApiResourceId", + table: "ApiSecrets", + column: "ApiResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientClaims_ClientId", + table: "ClientClaims", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientCorsOrigins_ClientId", + table: "ClientCorsOrigins", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientGrantTypes_ClientId", + table: "ClientGrantTypes", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientIdPRestrictions_ClientId", + table: "ClientIdPRestrictions", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientPostLogoutRedirectUris_ClientId", + table: "ClientPostLogoutRedirectUris", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientProperties_ClientId", + table: "ClientProperties", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientRedirectUris_ClientId", + table: "ClientRedirectUris", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_Clients_ClientId", + table: "Clients", + column: "ClientId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ClientScopes_ClientId", + table: "ClientScopes", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientSecrets_ClientId", + table: "ClientSecrets", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_IdentityClaims_IdentityResourceId", + table: "IdentityClaims", + column: "IdentityResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_IdentityProperties_IdentityResourceId", + table: "IdentityProperties", + column: "IdentityResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_IdentityResources_Name", + table: "IdentityResources", + column: "Name", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ApiClaims"); + + migrationBuilder.DropTable( + name: "ApiProperties"); + + migrationBuilder.DropTable( + name: "ApiScopeClaims"); + + migrationBuilder.DropTable( + name: "ApiSecrets"); + + migrationBuilder.DropTable( + name: "ClientClaims"); + + migrationBuilder.DropTable( + name: "ClientCorsOrigins"); + + migrationBuilder.DropTable( + name: "ClientGrantTypes"); + + migrationBuilder.DropTable( + name: "ClientIdPRestrictions"); + + migrationBuilder.DropTable( + name: "ClientPostLogoutRedirectUris"); + + migrationBuilder.DropTable( + name: "ClientProperties"); + + migrationBuilder.DropTable( + name: "ClientRedirectUris"); + + migrationBuilder.DropTable( + name: "ClientScopes"); + + migrationBuilder.DropTable( + name: "ClientSecrets"); + + migrationBuilder.DropTable( + name: "IdentityClaims"); + + migrationBuilder.DropTable( + name: "IdentityProperties"); + + migrationBuilder.DropTable( + name: "ApiScopes"); + + migrationBuilder.DropTable( + name: "Clients"); + + migrationBuilder.DropTable( + name: "IdentityResources"); + + migrationBuilder.DropTable( + name: "ApiResources"); + } + } +} diff --git a/IdentityServer/Data/Migrations/IdentityServer/ConfigurationDb/ConfigurationDbContextModelSnapshot.cs b/IdentityServer/Data/Migrations/IdentityServer/ConfigurationDb/ConfigurationDbContextModelSnapshot.cs new file mode 100644 index 0000000..ddd8206 --- /dev/null +++ b/IdentityServer/Data/Migrations/IdentityServer/ConfigurationDb/ConfigurationDbContextModelSnapshot.cs @@ -0,0 +1,677 @@ +// +using System; +using IdentityServer4.EntityFramework.DbContexts; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Teknik.IdentityServer.Data.Migrations.IdentityServer.ConfigurationDb +{ + [DbContext(typeof(ConfigurationDbContext))] + partial class ConfigurationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.0-preview2-35157") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Created"); + + b.Property("Description") + .HasMaxLength(1000); + + b.Property("DisplayName") + .HasMaxLength(200); + + b.Property("Enabled"); + + b.Property("LastAccessed"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200); + + b.Property("Updated"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ApiResources"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ApiResourceId"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(200); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ApiResourceId"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2000); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiProperties"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ApiResourceId"); + + b.Property("Description") + .HasMaxLength(1000); + + b.Property("DisplayName") + .HasMaxLength(200); + + b.Property("Emphasize"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200); + + b.Property("Required"); + + b.Property("ShowInDiscoveryDocument"); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ApiScopes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ApiScopeId"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(200); + + b.HasKey("Id"); + + b.HasIndex("ApiScopeId"); + + b.ToTable("ApiScopeClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ApiResourceId"); + + b.Property("Created"); + + b.Property("Description") + .HasMaxLength(1000); + + b.Property("Expiration"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250); + + b.Property("Value") + .IsRequired() + .HasMaxLength(4000); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiSecrets"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AbsoluteRefreshTokenLifetime"); + + b.Property("AccessTokenLifetime"); + + b.Property("AccessTokenType"); + + b.Property("AllowAccessTokensViaBrowser"); + + b.Property("AllowOfflineAccess"); + + b.Property("AllowPlainTextPkce"); + + b.Property("AllowRememberConsent"); + + b.Property("AlwaysIncludeUserClaimsInIdToken"); + + b.Property("AlwaysSendClientClaims"); + + b.Property("AuthorizationCodeLifetime"); + + b.Property("BackChannelLogoutSessionRequired"); + + b.Property("BackChannelLogoutUri") + .HasMaxLength(2000); + + b.Property("ClientClaimsPrefix") + .HasMaxLength(200); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200); + + b.Property("ClientName") + .HasMaxLength(200); + + b.Property("ClientUri") + .HasMaxLength(2000); + + b.Property("ConsentLifetime"); + + b.Property("Created"); + + b.Property("Description") + .HasMaxLength(1000); + + b.Property("EnableLocalLogin"); + + b.Property("Enabled"); + + b.Property("FrontChannelLogoutSessionRequired"); + + b.Property("FrontChannelLogoutUri") + .HasMaxLength(2000); + + b.Property("IdentityTokenLifetime"); + + b.Property("IncludeJwtId"); + + b.Property("LastAccessed"); + + b.Property("LogoUri") + .HasMaxLength(2000); + + b.Property("PairWiseSubjectSalt") + .HasMaxLength(200); + + b.Property("ProtocolType") + .IsRequired() + .HasMaxLength(200); + + b.Property("RefreshTokenExpiration"); + + b.Property("RefreshTokenUsage"); + + b.Property("RequireClientSecret"); + + b.Property("RequireConsent"); + + b.Property("RequirePkce"); + + b.Property("SlidingRefreshTokenLifetime"); + + b.Property("UpdateAccessTokenClaimsOnRefresh"); + + b.Property("Updated"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.ToTable("Clients"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClientId"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250); + + b.Property("Value") + .IsRequired() + .HasMaxLength(250); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClientId"); + + b.Property("Origin") + .IsRequired() + .HasMaxLength(150); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientCorsOrigins"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClientId"); + + b.Property("GrantType") + .IsRequired() + .HasMaxLength(250); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientGrantTypes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClientId"); + + b.Property("Provider") + .IsRequired() + .HasMaxLength(200); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientIdPRestrictions"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClientId"); + + b.Property("PostLogoutRedirectUri") + .IsRequired() + .HasMaxLength(2000); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientPostLogoutRedirectUris"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClientId"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2000); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientProperties"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClientId"); + + b.Property("RedirectUri") + .IsRequired() + .HasMaxLength(2000); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientRedirectUris"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClientId"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(200); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientScopes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClientId"); + + b.Property("Created"); + + b.Property("Description") + .HasMaxLength(2000); + + b.Property("Expiration"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250); + + b.Property("Value") + .IsRequired() + .HasMaxLength(4000); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientSecrets"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("IdentityResourceId"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(200); + + b.HasKey("Id"); + + b.HasIndex("IdentityResourceId"); + + b.ToTable("IdentityClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Created"); + + b.Property("Description") + .HasMaxLength(1000); + + b.Property("DisplayName") + .HasMaxLength(200); + + b.Property("Emphasize"); + + b.Property("Enabled"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200); + + b.Property("Required"); + + b.Property("ShowInDiscoveryDocument"); + + b.Property("Updated"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("IdentityResources"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("IdentityResourceId"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2000); + + b.HasKey("Id"); + + b.HasIndex("IdentityResourceId"); + + b.ToTable("IdentityProperties"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("UserClaims") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("Properties") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("Scopes") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "ApiScope") + .WithMany("UserClaims") + .HasForeignKey("ApiScopeId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiSecret", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("Secrets") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("Claims") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("AllowedCorsOrigins") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("AllowedGrantTypes") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("IdentityProviderRestrictions") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("PostLogoutRedirectUris") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("Properties") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("RedirectUris") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("AllowedScopes") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("ClientSecrets") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") + .WithMany("UserClaims") + .HasForeignKey("IdentityResourceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") + .WithMany("Properties") + .HasForeignKey("IdentityResourceId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/IdentityServer/Data/Migrations/IdentityServer/PersistedGrantDb/20180930040529_InitialIdentityServerPersistedGrantDbMigration.Designer.cs b/IdentityServer/Data/Migrations/IdentityServer/PersistedGrantDb/20180930040529_InitialIdentityServerPersistedGrantDbMigration.Designer.cs new file mode 100644 index 0000000..99c3db9 --- /dev/null +++ b/IdentityServer/Data/Migrations/IdentityServer/PersistedGrantDb/20180930040529_InitialIdentityServerPersistedGrantDbMigration.Designer.cs @@ -0,0 +1,57 @@ +// +using System; +using IdentityServer4.EntityFramework.DbContexts; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Teknik.IdentityServer.Data.Migrations.IdentityServer.PersistedGrantDb +{ + [DbContext(typeof(PersistedGrantDbContext))] + [Migration("20180930040529_InitialIdentityServerPersistedGrantDbMigration")] + partial class InitialIdentityServerPersistedGrantDbMigration + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.0-preview2-35157") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b => + { + b.Property("Key") + .HasMaxLength(200); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200); + + b.Property("CreationTime"); + + b.Property("Data") + .IsRequired() + .HasMaxLength(50000); + + b.Property("Expiration"); + + b.Property("SubjectId") + .HasMaxLength(200); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50); + + b.HasKey("Key"); + + b.HasIndex("SubjectId", "ClientId", "Type"); + + b.ToTable("PersistedGrants"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/IdentityServer/Data/Migrations/IdentityServer/PersistedGrantDb/20180930040529_InitialIdentityServerPersistedGrantDbMigration.cs b/IdentityServer/Data/Migrations/IdentityServer/PersistedGrantDb/20180930040529_InitialIdentityServerPersistedGrantDbMigration.cs new file mode 100644 index 0000000..df613a6 --- /dev/null +++ b/IdentityServer/Data/Migrations/IdentityServer/PersistedGrantDb/20180930040529_InitialIdentityServerPersistedGrantDbMigration.cs @@ -0,0 +1,39 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Teknik.IdentityServer.Data.Migrations.IdentityServer.PersistedGrantDb +{ + public partial class InitialIdentityServerPersistedGrantDbMigration : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "PersistedGrants", + columns: table => new + { + Key = table.Column(maxLength: 200, nullable: false), + Type = table.Column(maxLength: 50, nullable: false), + SubjectId = table.Column(maxLength: 200, nullable: true), + ClientId = table.Column(maxLength: 200, nullable: false), + CreationTime = table.Column(nullable: false), + Expiration = table.Column(nullable: true), + Data = table.Column(maxLength: 50000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PersistedGrants", x => x.Key); + }); + + migrationBuilder.CreateIndex( + name: "IX_PersistedGrants_SubjectId_ClientId_Type", + table: "PersistedGrants", + columns: new[] { "SubjectId", "ClientId", "Type" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PersistedGrants"); + } + } +} diff --git a/IdentityServer/Data/Migrations/IdentityServer/PersistedGrantDb/PersistedGrantDbContextModelSnapshot.cs b/IdentityServer/Data/Migrations/IdentityServer/PersistedGrantDb/PersistedGrantDbContextModelSnapshot.cs new file mode 100644 index 0000000..9282639 --- /dev/null +++ b/IdentityServer/Data/Migrations/IdentityServer/PersistedGrantDb/PersistedGrantDbContextModelSnapshot.cs @@ -0,0 +1,55 @@ +// +using System; +using IdentityServer4.EntityFramework.DbContexts; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Teknik.IdentityServer.Data.Migrations.IdentityServer.PersistedGrantDb +{ + [DbContext(typeof(PersistedGrantDbContext))] + partial class PersistedGrantDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.0-preview2-35157") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b => + { + b.Property("Key") + .HasMaxLength(200); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200); + + b.Property("CreationTime"); + + b.Property("Data") + .IsRequired() + .HasMaxLength(50000); + + b.Property("Expiration"); + + b.Property("SubjectId") + .HasMaxLength(200); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50); + + b.HasKey("Key"); + + b.HasIndex("SubjectId", "ClientId", "Type"); + + b.ToTable("PersistedGrants"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/IdentityServer/IdentityServer.csproj b/IdentityServer/IdentityServer.csproj new file mode 100644 index 0000000..9fa0e95 --- /dev/null +++ b/IdentityServer/IdentityServer.csproj @@ -0,0 +1,34 @@ + + + + netcoreapp2.1 + Teknik.IdentityServer + Teknik.IdentityServer + win-x86;win-x64;linux-x64;linux-arm;osx-x64 + Debug;Release;Test + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/IdentityServer/Images/favicon.ico b/IdentityServer/Images/favicon.ico new file mode 100644 index 0000000..cb02120 Binary files /dev/null and b/IdentityServer/Images/favicon.ico differ diff --git a/IdentityServer/Images/logo-black.svg b/IdentityServer/Images/logo-black.svg new file mode 100644 index 0000000..2a5a4dc --- /dev/null +++ b/IdentityServer/Images/logo-black.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + diff --git a/IdentityServer/Images/logo-blue.svg b/IdentityServer/Images/logo-blue.svg new file mode 100644 index 0000000..1b0eb1f --- /dev/null +++ b/IdentityServer/Images/logo-blue.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + diff --git a/IdentityServer/Middleware/BlacklistMiddleware.cs b/IdentityServer/Middleware/BlacklistMiddleware.cs new file mode 100644 index 0000000..9c855cd --- /dev/null +++ b/IdentityServer/Middleware/BlacklistMiddleware.cs @@ -0,0 +1,118 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Caching.Memory; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Teknik.Configuration; + +namespace Teknik.IdentityServer.Middleware +{ + public class BlacklistMiddleware + { + private readonly RequestDelegate _next; + private readonly IMemoryCache _cache; + + public BlacklistMiddleware(RequestDelegate next, IMemoryCache cache) + { + _next = next; + _cache = cache; + } + + public async Task Invoke(HttpContext context, Config config) + { + // Beggining of Request + bool blocked = false; + string blockReason = string.Empty; + + #region Detect Blacklisted IPs + if (!blocked) + { + string IPAddr = context.Request.HttpContext.Connection.RemoteIpAddress.ToString(); + if (!string.IsNullOrEmpty(IPAddr)) + { + StringDictionary badIPs = GetFileData(context, "BlockedIPs", config.IPBlacklistFile); + + blocked |= (badIPs != null && badIPs.ContainsKey(IPAddr)); + blockReason = $"This IP address ({IPAddr}) has been blacklisted. If you feel this is in error, please contact support@teknik.io for assistance."; + } + } + #endregion + + #region Detect Blacklisted Referrers + if (!blocked) + { + string referrer = context.Request.Headers["Referer"].ToString(); + if (!string.IsNullOrEmpty(referrer)) + { + StringDictionary badReferrers = GetFileData(context, "BlockedReferrers", config.ReferrerBlacklistFile); + + blocked |= (badReferrers != null && badReferrers.ContainsKey(referrer)); + blockReason = $"This referrer ({referrer}) has been blacklisted. If you feel this is in error, please contact support@teknik.io for assistance."; + } + } + #endregion + + if (blocked) + { + // Clear the response + context.Response.Clear(); + + string jsonResult = JsonConvert.SerializeObject(new { error = new { type = "Blacklist", message = blockReason } }); + await context.Response.WriteAsync(jsonResult); + return; + } + + await _next.Invoke(context); + + // End of request + } + + public StringDictionary GetFileData(HttpContext context, string key, string filePath) + { + StringDictionary data; + if (!_cache.TryGetValue(key, out data)) + { + data = GetFileLines(filePath); + _cache.Set(key, data); + } + + return data; + } + + public StringDictionary GetFileLines(string configPath) + { + StringDictionary retval = new StringDictionary(); + if (File.Exists(configPath)) + { + using (StreamReader sr = new StreamReader(configPath)) + { + String line; + while ((line = sr.ReadLine()) != null) + { + line = line.Trim(); + if (line.Length != 0) + { + retval.Add(line, null); + } + } + } + } + + return retval; + } + } + + public static class BlacklistMiddlewareExtensions + { + public static IApplicationBuilder UseBlacklist(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/IdentityServer/Middleware/CORSMiddleware.cs b/IdentityServer/Middleware/CORSMiddleware.cs new file mode 100644 index 0000000..e40c3b5 --- /dev/null +++ b/IdentityServer/Middleware/CORSMiddleware.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Teknik.Configuration; +using Teknik.Utilities; + +namespace Teknik.IdentityServer.Middleware +{ + // You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project + public class CORSMiddleware + { + private readonly RequestDelegate _next; + + public CORSMiddleware(RequestDelegate next) + { + _next = next; + } + + public Task InvokeAsync(HttpContext httpContext, Config config) + { + // Allow this domain, or everything if local + string origin = (httpContext.Request.IsLocal()) ? "*" : httpContext.Request.Headers["Origin"].ToString(); + + // Is the referrer set to the CDN and we are using a CDN? + if (config.UseCdn && !string.IsNullOrEmpty(config.CdnHost)) + { + try + { + string host = httpContext.Request.Headers["Host"]; + Uri uri = new Uri(config.CdnHost); + if (host == uri.Host) + origin = host; + } + catch { } + } + + string domain = (string.IsNullOrEmpty(origin)) ? string.Empty : origin.GetDomain(); + + if (string.IsNullOrEmpty(origin)) + { + string host = httpContext.Request.Headers["Host"]; + string sub = host.GetSubdomain(); + origin = (string.IsNullOrEmpty(sub)) ? config.Host : sub + "." + config.Host; + } + else + { + if (domain != config.Host) + { + string sub = origin.GetSubdomain(); + origin = (string.IsNullOrEmpty(sub)) ? config.Host : sub + "." + config.Host; + } + } + + httpContext.Response.Headers.Append("Access-Control-Allow-Origin", origin); + httpContext.Response.Headers.Append("Vary", "Origin"); + + return _next(httpContext); + } + } + + // Extension method used to add the middleware to the HTTP request pipeline. + public static class CORSMiddlewareExtensions + { + public static IApplicationBuilder UseCORS(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/IdentityServer/Middleware/CSPMiddleware.cs b/IdentityServer/Middleware/CSPMiddleware.cs new file mode 100644 index 0000000..e3f424e --- /dev/null +++ b/IdentityServer/Middleware/CSPMiddleware.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Teknik.Configuration; +using Teknik.Utilities; + +namespace Teknik.IdentityServer.Middleware +{ + // You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project + public class CSPMiddleware + { + private readonly RequestDelegate _next; + + public CSPMiddleware(RequestDelegate next) + { + _next = next; + } + + public Task Invoke(HttpContext httpContext, Config config) + { + if (!httpContext.Request.IsLocal()) + { + // Default to nothing allowed + string allowedDomain = "'none'"; + + // Allow this domain + string host = httpContext.Request.Headers["Host"]; + + if (!string.IsNullOrEmpty(host)) + { + allowedDomain = host; + } + + var csp = "default-src 'self';" + + "img-src * 'self' data: https:;" + + $"style-src 'self' {allowedDomain};" + + $"font-src 'self' {allowedDomain};" + + $"script-src 'self' 'unsafe-inline' {allowedDomain};"; + + if (!httpContext.Response.Headers.ContainsKey("Content-Security-Policy")) + { + httpContext.Response.Headers.Add("Content-Security-Policy", csp); + } + // and once again for IE + if (!httpContext.Response.Headers.ContainsKey("X-Content-Security-Policy")) + { + httpContext.Response.Headers.Add("X-Content-Security-Policy", csp); + } + } + + return _next(httpContext); + } + } + + // Extension method used to add the middleware to the HTTP request pipeline. + public static class CSPMiddlewareExtensions + { + public static IApplicationBuilder UseCSP(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/IdentityServer/Middleware/ErrorHandlerMiddleware.cs b/IdentityServer/Middleware/ErrorHandlerMiddleware.cs new file mode 100644 index 0000000..3b7c3ab --- /dev/null +++ b/IdentityServer/Middleware/ErrorHandlerMiddleware.cs @@ -0,0 +1,105 @@ +using IdentityServer4.Services; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Teknik.Configuration; +using Teknik.IdentityServer.Controllers; +using Teknik.Logging; +using Teknik.Utilities; + +namespace Teknik.IdentityServer.Middleware +{ + public class ErrorHandlerMiddleware + { + private readonly RequestDelegate _next; + private readonly IRouter _router; + + public ErrorHandlerMiddleware(RequestDelegate next, IRouter router) + { + _next = next; + _router = router; + } + + public async Task Invoke(HttpContext httpContext, ILogger logger, Config config, IIdentityServerInteractionService interaction) + { + var statusCodeFeature = new StatusCodePagesFeature(); + httpContext.Features.Set(statusCodeFeature); + + Exception exception = null; + try + { + await _next(httpContext); + } + catch (Exception ex) + { + httpContext.Response.StatusCode = 500; + exception = ex; + } + + if (!statusCodeFeature.Enabled) + { + // Check if the feature is still available because other middleware (such as a web API written in MVC) could + // have disabled the feature to prevent HTML status code responses from showing up to an API client. + return; + } + + // Do nothing if a response body has already been provided or not 404 response + if (httpContext.Response.HasStarted) + { + return; + } + + // Detect if there is a response code or exception occured + if ((httpContext.Response.StatusCode >= 400 && httpContext.Response.StatusCode <= 600) || exception != null) + { + RouteData routeData = new RouteData(); + routeData.Values.Add("controller", "Error"); + routeData.Routers.Add(_router); + + var context = new ControllerContext(); + context.HttpContext = httpContext; + context.RouteData = routeData; + context.ActionDescriptor = new Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor(); + + ErrorController errorController = new ErrorController(logger, config, interaction); + errorController.ControllerContext = context; + + if (httpContext.Response.StatusCode == 500) + { + await errorController.Http500(exception).ExecuteResultAsync(context); + } + else + { + await errorController.HttpError(httpContext.Response.StatusCode).ExecuteResultAsync(context); + } + } + } + } + + // Extension method used to add the middleware to the HTTP request pipeline. + public static class SetupErrorHandlerMiddlewareExtensions + { + public static IApplicationBuilder UseErrorHandler(this IApplicationBuilder builder, Config config) + { + var routes = new RouteBuilder(builder) + { + DefaultHandler = builder.ApplicationServices.GetRequiredService(), + }; + + return builder.UseMiddleware(routes.Build()); + } + } +} diff --git a/IdentityServer/Middleware/PerformanceMonitorMiddleware.cs b/IdentityServer/Middleware/PerformanceMonitorMiddleware.cs new file mode 100644 index 0000000..fa13d58 --- /dev/null +++ b/IdentityServer/Middleware/PerformanceMonitorMiddleware.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Teknik.Configuration; +using Teknik.Utilities; + +namespace Teknik.IdentityServer.Middleware +{ + // You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project + public class PerformanceMonitorMiddleware + { + private readonly RequestDelegate _next; + + public PerformanceMonitorMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task Invoke(HttpContext httpContext, Config config) + { + Stopwatch timer = new Stopwatch(); + timer.Start(); + + httpContext.Response.OnStarting(state => + { + var context = (HttpContext)state; + + timer.Stop(); + + double ms = (double)timer.ElapsedMilliseconds; + string result = string.Format("{0:F0}", ms); + + if (!httpContext.Response.Headers.IsReadOnly) + httpContext.Response.Headers.Add("GenerationTime", result); + + return Task.CompletedTask; + }, httpContext); + + await _next(httpContext); + + // Don't interfere with non-HTML responses + if (httpContext.Response.ContentType != null && httpContext.Response.ContentType.StartsWith("text/html") && httpContext.Response.StatusCode == 200 && !httpContext.Request.IsAjaxRequest()) + { + double ms = (double)timer.ElapsedMilliseconds; + string result = string.Format("{0:F0}", ms); + + await httpContext.Response.WriteAsync( + ""); + } + } + } + + // Extension method used to add the middleware to the HTTP request pipeline. + public static class PerformanceMonitorMiddlewareExtensions + { + public static IApplicationBuilder UsePerformanceMonitor(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/IdentityServer/Middleware/SecurityHeadersMiddleware.cs b/IdentityServer/Middleware/SecurityHeadersMiddleware.cs new file mode 100644 index 0000000..dd88b3a --- /dev/null +++ b/IdentityServer/Middleware/SecurityHeadersMiddleware.cs @@ -0,0 +1,61 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Teknik.Configuration; + +namespace Teknik.IdentityServer.Middleware +{ + public class SecurityHeadersMiddleware + { + private readonly RequestDelegate _next; + + public SecurityHeadersMiddleware(RequestDelegate next) + { + _next = next; + } + + public Task Invoke(HttpContext httpContext, Config config) + { + IHeaderDictionary headers = httpContext.Response.Headers; + + // Access Control + headers.Append("Access-Control-Allow-Credentials", "true"); + headers.Append("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE, OPTIONS"); + headers.Append("Access-Control-Allow-Headers", "Authorization, Accept, Origin, Content-Type, X-Requested-With, Connection, Transfer-Encoding"); + + // HSTS + headers.Append("strict-transport-security", "max-age=31536000; includeSubdomains; preload"); + + // XSS Protection + headers.Append("X-XSS-Protection", "1; mode=block"); + + // Content Type Options + headers.Append("X-Content-Type-Options", "nosniff"); + + // Public Key Pinning + string keys = string.Empty; + foreach (string key in config.PublicKeys) + { + keys += $"pin-sha256=\"{key}\";"; + } + headers.Append("Public-Key-Pins", $"max-age=300; includeSubDomains; {keys}"); + + // Referrer Policy + headers.Append("Referrer-Policy", "no-referrer, strict-origin-when-cross-origin"); + + return _next(httpContext); + } + } + + // Extension method used to add the middleware to the HTTP request pipeline. + public static class SecurityHeadersMiddlewareExtensions + { + public static IApplicationBuilder UseSecurityHeaders(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/IdentityServer/Middleware/SetupHttpContextMiddleware.cs b/IdentityServer/Middleware/SetupHttpContextMiddleware.cs new file mode 100644 index 0000000..4eb31d4 --- /dev/null +++ b/IdentityServer/Middleware/SetupHttpContextMiddleware.cs @@ -0,0 +1,40 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Teknik.Configuration; +using Teknik.Utilities; + +namespace Teknik.IdentityServer.Middleware +{ + public class SetupHttpContextMiddleware + { + private readonly RequestDelegate _next; + + public SetupHttpContextMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task Invoke(HttpContext httpContext) + { + // Generate the NONCE used for this request + string nonce = Convert.ToBase64String(Encoding.UTF8.GetBytes(StringHelper.RandomString(24))); + httpContext.Items[Constants.NONCE_KEY] = nonce; + + await _next(httpContext); + } + } + + // Extension method used to add the middleware to the HTTP request pipeline. + public static class SetupHttpContextMiddlewareExtensions + { + public static IApplicationBuilder UseHttpContextSetup(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/IdentityServer/Models/ApplicationUser.cs b/IdentityServer/Models/ApplicationUser.cs new file mode 100644 index 0000000..f60839e --- /dev/null +++ b/IdentityServer/Models/ApplicationUser.cs @@ -0,0 +1,74 @@ +using Microsoft.AspNetCore.Identity; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Teknik.Utilities; + +namespace Teknik.IdentityServer.Models +{ + public class ApplicationUser : IdentityUser + { + public DateTime CreationDate { get; set; } + + public DateTime LastSeen { get; set; } + + public AccountType AccountType { get; set; } + + public AccountStatus AccountStatus { get; set; } + + public string PGPPublicKey { get; set; } + + public ApplicationUser() : base() + { + Init(); + } + + public ApplicationUser(string userName) : base(userName) + { + Init(); + } + + private void Init() + { + CreationDate = DateTime.Now; + LastSeen = DateTime.Now; + AccountType = AccountType.Basic; + AccountStatus = AccountStatus.Active; + PGPPublicKey = null; + } + + public List ToClaims() + { + var claims = new List(); + claims.Add(new Claim("username", UserName)); + claims.Add(new Claim("creation-date", CreationDate.ToString("o"))); + claims.Add(new Claim("last-seen", LastSeen.ToString("o"))); + claims.Add(new Claim("account-type", AccountType.ToString())); + claims.Add(new Claim("account-status", AccountStatus.ToString())); + claims.Add(new Claim("recovery-email", Email ?? string.Empty)); + claims.Add(new Claim("recovery-verified", EmailConfirmed.ToString())); + claims.Add(new Claim("2fa-enabled", TwoFactorEnabled.ToString())); + claims.Add(new Claim("pgp-public-key", PGPPublicKey ?? string.Empty)); + return claims; + } + + public JObject ToJson() + { + return new JObject() + { + new JProperty("username", UserName), + new JProperty("creation-date", CreationDate), + new JProperty("last-seen", LastSeen), + new JProperty("account-type", AccountType), + new JProperty("account-status", AccountStatus), + new JProperty("recovery-email", Email), + new JProperty("recovery-verified", EmailConfirmed), + new JProperty("2fa-enabled", TwoFactorEnabled), + new JProperty("pgp-public-key", PGPPublicKey) + }; + } + } +} diff --git a/IdentityServer/Models/ConsentInputModel.cs b/IdentityServer/Models/ConsentInputModel.cs new file mode 100644 index 0000000..b390515 --- /dev/null +++ b/IdentityServer/Models/ConsentInputModel.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Teknik.IdentityServer.Models +{ + public class ConsentInputModel + { + public string Button { get; set; } + public IEnumerable ScopesConsented { get; set; } + public bool RememberConsent { get; set; } + public string ReturnUrl { get; set; } + } +} \ No newline at end of file diff --git a/IdentityServer/Models/LoginInputModel.cs b/IdentityServer/Models/LoginInputModel.cs new file mode 100644 index 0000000..02288c1 --- /dev/null +++ b/IdentityServer/Models/LoginInputModel.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace Teknik.IdentityServer.Models +{ + public class LoginInputModel + { + [Required] + public string Username { get; set; } + [Required] + public string Password { get; set; } + public bool RememberMe { get; set; } + public string ReturnUrl { get; set; } + } +} \ No newline at end of file diff --git a/IdentityServer/Models/LogoutInputModel.cs b/IdentityServer/Models/LogoutInputModel.cs new file mode 100644 index 0000000..7ffb622 --- /dev/null +++ b/IdentityServer/Models/LogoutInputModel.cs @@ -0,0 +1,8 @@ +namespace Teknik.IdentityServer.Models +{ + public class LogoutInputModel + { + public string LogoutId { get; set; } + public string ReturnURL { get; set; } + } +} diff --git a/IdentityServer/Models/Manage/CheckPasswordModel.cs b/IdentityServer/Models/Manage/CheckPasswordModel.cs new file mode 100644 index 0000000..29a2545 --- /dev/null +++ b/IdentityServer/Models/Manage/CheckPasswordModel.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Teknik.IdentityServer.Models.Manage +{ + public class CheckPasswordModel + { + public string Username { get; set; } + public string Password { get; set; } + } +} diff --git a/IdentityServer/Models/Manage/CreateClientModel.cs b/IdentityServer/Models/Manage/CreateClientModel.cs new file mode 100644 index 0000000..7dc7a10 --- /dev/null +++ b/IdentityServer/Models/Manage/CreateClientModel.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Teknik.IdentityServer.Models.Manage +{ + public class CreateClientModel + { + } +} diff --git a/IdentityServer/Models/Manage/DeleteUserModel.cs b/IdentityServer/Models/Manage/DeleteUserModel.cs new file mode 100644 index 0000000..67b4319 --- /dev/null +++ b/IdentityServer/Models/Manage/DeleteUserModel.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Teknik.IdentityServer.Models.Manage +{ + public class DeleteUserModel + { + public string Username { get; set; } + } +} diff --git a/IdentityServer/Models/Manage/Disable2FAModel.cs b/IdentityServer/Models/Manage/Disable2FAModel.cs new file mode 100644 index 0000000..6d4dd6d --- /dev/null +++ b/IdentityServer/Models/Manage/Disable2FAModel.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Teknik.IdentityServer.Models.Manage +{ + public class Disable2FAModel + { + public string Username { get; set; } + } +} diff --git a/IdentityServer/Models/Manage/Enable2FAModel.cs b/IdentityServer/Models/Manage/Enable2FAModel.cs new file mode 100644 index 0000000..7bae8aa --- /dev/null +++ b/IdentityServer/Models/Manage/Enable2FAModel.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Teknik.IdentityServer.Models.Manage +{ + public class Enable2FAModel + { + public string Username { get; set; } + public string Code { get; set; } + } +} diff --git a/IdentityServer/Models/Manage/GeneratePasswordResetTokenModel.cs b/IdentityServer/Models/Manage/GeneratePasswordResetTokenModel.cs new file mode 100644 index 0000000..50041c1 --- /dev/null +++ b/IdentityServer/Models/Manage/GeneratePasswordResetTokenModel.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Teknik.IdentityServer.Models.Manage +{ + public class GeneratePasswordResetTokenModel + { + public string Username { get; set; } + } +} diff --git a/IdentityServer/Models/Manage/GenerateRecoveryCodesModel.cs b/IdentityServer/Models/Manage/GenerateRecoveryCodesModel.cs new file mode 100644 index 0000000..9130e2c --- /dev/null +++ b/IdentityServer/Models/Manage/GenerateRecoveryCodesModel.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Teknik.IdentityServer.Models.Manage +{ + public class GenerateRecoveryCodesModel + { + public string Username { get; set; } + } +} diff --git a/IdentityServer/Models/Manage/NewUserModel.cs b/IdentityServer/Models/Manage/NewUserModel.cs new file mode 100644 index 0000000..6638e6b --- /dev/null +++ b/IdentityServer/Models/Manage/NewUserModel.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Teknik.Utilities; + +namespace Teknik.IdentityServer.Models.Manage +{ + public class NewUserModel + { + public string Username { get; set; } + public string Password { get; set; } + public AccountType AccountType { get; set; } + public AccountStatus AccountStatus { get; set; } + public string RecoveryEmail { get; set; } + public bool RecoveryVerified { get; set; } + public string PGPPublicKey { get; set; } + + public NewUserModel() + { + AccountType = AccountType.Basic; + AccountStatus = AccountStatus.Active; + } + } +} diff --git a/IdentityServer/Models/Manage/Reset2FAKeyModel.cs b/IdentityServer/Models/Manage/Reset2FAKeyModel.cs new file mode 100644 index 0000000..c455f68 --- /dev/null +++ b/IdentityServer/Models/Manage/Reset2FAKeyModel.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Teknik.IdentityServer.Models.Manage +{ + public class Reset2FAKeyModel + { + public string Username { get; set; } + } +} diff --git a/IdentityServer/Models/Manage/ResetPasswordModel.cs b/IdentityServer/Models/Manage/ResetPasswordModel.cs new file mode 100644 index 0000000..6a020a2 --- /dev/null +++ b/IdentityServer/Models/Manage/ResetPasswordModel.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Teknik.IdentityServer.Models.Manage +{ + public class ResetPasswordModel + { + public string Username { get; set; } + public string Token { get; set; } + public string Password { get; set; } + } +} diff --git a/IdentityServer/Models/Manage/UpdateAccountStatusModel.cs b/IdentityServer/Models/Manage/UpdateAccountStatusModel.cs new file mode 100644 index 0000000..24b6eb1 --- /dev/null +++ b/IdentityServer/Models/Manage/UpdateAccountStatusModel.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Teknik.Utilities; + +namespace Teknik.IdentityServer.Models.Manage +{ + public class UpdateAccountStatusModel + { + public string Username { get; set; } + public AccountStatus AccountStatus { get; set; } + } +} diff --git a/IdentityServer/Models/Manage/UpdateAccountTypeModel.cs b/IdentityServer/Models/Manage/UpdateAccountTypeModel.cs new file mode 100644 index 0000000..e6b8104 --- /dev/null +++ b/IdentityServer/Models/Manage/UpdateAccountTypeModel.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Teknik.Utilities; + +namespace Teknik.IdentityServer.Models.Manage +{ + public class UpdateAccountTypeModel + { + public string Username { get; set; } + public AccountType AccountType { get; set; } + } +} diff --git a/IdentityServer/Models/Manage/UpdateEmailModel.cs b/IdentityServer/Models/Manage/UpdateEmailModel.cs new file mode 100644 index 0000000..625e7e9 --- /dev/null +++ b/IdentityServer/Models/Manage/UpdateEmailModel.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Teknik.IdentityServer.Models.Manage +{ + public class UpdateEmailModel + { + public string Username { get; set; } + public string Email { get; set; } + } +} diff --git a/IdentityServer/Models/Manage/UpdateEmailVerifiedModel.cs b/IdentityServer/Models/Manage/UpdateEmailVerifiedModel.cs new file mode 100644 index 0000000..efacd49 --- /dev/null +++ b/IdentityServer/Models/Manage/UpdateEmailVerifiedModel.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Teknik.IdentityServer.Models.Manage +{ + public class UpdateEmailVerifiedModel + { + public string Username { get; set; } + public bool Verified { get; set; } + } +} diff --git a/IdentityServer/Models/Manage/UpdatePGPPublicKeyModel.cs b/IdentityServer/Models/Manage/UpdatePGPPublicKeyModel.cs new file mode 100644 index 0000000..4e9a434 --- /dev/null +++ b/IdentityServer/Models/Manage/UpdatePGPPublicKeyModel.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Teknik.Utilities; + +namespace Teknik.IdentityServer.Models.Manage +{ + public class UpdatePGPPublicKeyModel + { + public string Username { get; set; } + public string PGPPublicKey { get; set; } + } +} diff --git a/IdentityServer/Models/Manage/UpdatePasswordModel.cs b/IdentityServer/Models/Manage/UpdatePasswordModel.cs new file mode 100644 index 0000000..7b65842 --- /dev/null +++ b/IdentityServer/Models/Manage/UpdatePasswordModel.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Teknik.IdentityServer.Models.Manage +{ + public class UpdatePasswordModel + { + public string Username { get; set; } + public string CurrentPassword { get; set; } + public string NewPassword { get; set; } + } +} diff --git a/IdentityServer/Models/Manage/VerifyEmailModel.cs b/IdentityServer/Models/Manage/VerifyEmailModel.cs new file mode 100644 index 0000000..5ae1a42 --- /dev/null +++ b/IdentityServer/Models/Manage/VerifyEmailModel.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Teknik.IdentityServer.Models.Manage +{ + public class VerifyEmailModel + { + public string Username { get; set; } + public string Token { get; set; } + } +} diff --git a/IdentityServer/Models/ProcessConsentResult.cs b/IdentityServer/Models/ProcessConsentResult.cs new file mode 100644 index 0000000..ccc5b0e --- /dev/null +++ b/IdentityServer/Models/ProcessConsentResult.cs @@ -0,0 +1,16 @@ +using Teknik.IdentityServer.ViewModels; + +namespace Teknik.IdentityServer.Models +{ + public class ProcessConsentResult + { + public bool IsRedirect => RedirectUri != null; + public string RedirectUri { get; set; } + + public bool ShowView => ViewModel != null; + public ConsentViewModel ViewModel { get; set; } + + public bool HasValidationError => ValidationError != null; + public string ValidationError { get; set; } + } +} diff --git a/IdentityServer/Options/AccountOptions.cs b/IdentityServer/Options/AccountOptions.cs new file mode 100644 index 0000000..c2bc195 --- /dev/null +++ b/IdentityServer/Options/AccountOptions.cs @@ -0,0 +1,16 @@ +using System; + +namespace Teknik.IdentityServer.Options +{ + public class AccountOptions + { + public static bool AllowLocalLogin = true; + public static bool AllowRememberLogin = true; + public static TimeSpan RememberMeLoginDuration = TimeSpan.FromDays(30); + + public static bool ShowLogoutPrompt = true; + public static bool AutomaticRedirectAfterSignOut = true; + + public static string InvalidCredentialsErrorMessage = "Invalid username or password"; + } +} diff --git a/IdentityServer/Options/ConsentOptions.cs b/IdentityServer/Options/ConsentOptions.cs new file mode 100644 index 0000000..e7fe7ae --- /dev/null +++ b/IdentityServer/Options/ConsentOptions.cs @@ -0,0 +1,12 @@ +namespace Teknik.IdentityServer.Options +{ + public class ConsentOptions + { + public static bool EnableOfflineAccess = true; + public static string OfflineAccessDisplayName = "Offline Access"; + public static string OfflineAccessDescription = "Access to your applications and resources, even when you are offline"; + + public static readonly string MustChooseOneErrorMessage = "You must pick at least one permission"; + public static readonly string InvalidSelectionErrorMessage = "Invalid selection"; + } +} diff --git a/IdentityServer/Program.cs b/IdentityServer/Program.cs new file mode 100644 index 0000000..2c3c7e8 --- /dev/null +++ b/IdentityServer/Program.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; + +namespace Teknik.IdentityServer +{ + public class Program + { + public static void Main(string[] args) + { + BuildWebHost(args).Run(); + } + + public static IWebHost BuildWebHost(string[] args) + { + var config = new ConfigurationBuilder() + .AddJsonFile("appsettings.json", optional: true) + .AddCommandLine(args) + .Build(); + + return WebHost.CreateDefaultBuilder(args) + .UseConfiguration(config) + .UseStartup() + .Build(); + } + } +} diff --git a/IdentityServer/Properties/PublishProfiles/Teknik Identity Development.pubxml b/IdentityServer/Properties/PublishProfiles/Teknik Identity Development.pubxml new file mode 100644 index 0000000..57af2fd --- /dev/null +++ b/IdentityServer/Properties/PublishProfiles/Teknik Identity Development.pubxml @@ -0,0 +1,27 @@ + + + + + MSDeploy + Release + Any CPU + https://authdev.teknik.io + True + False + netcoreapp2.1 + 05842e03-223a-4f43-9e81-d968a9475a97 + false + <_IsPortable>true + ams1.teknik.io + TeknikIdentityDev + + True + WMSVC + True + Administrator + <_SavePWD>True + + \ No newline at end of file diff --git a/IdentityServer/Properties/launchSettings.json b/IdentityServer/Properties/launchSettings.json new file mode 100644 index 0000000..6c18273 --- /dev/null +++ b/IdentityServer/Properties/launchSettings.json @@ -0,0 +1,35 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "https://localhost:44350/", + "sslPort": 44350 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IdentityServer": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:6002/" + }, + "IdentityServer - Prod": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Production" + }, + "applicationUrl": "https://localhost:6002/" + } + } +} \ No newline at end of file diff --git a/IdentityServer/Scripts/Error.js b/IdentityServer/Scripts/Error.js new file mode 100644 index 0000000..c90e3b1 --- /dev/null +++ b/IdentityServer/Scripts/Error.js @@ -0,0 +1,49 @@ +$(document).ready(function () { + $('#submitErrorReport').click(function () { + bootbox.prompt({ + title: "Please enter any additional information that could help us", + inputType: 'textarea', + callback: function (result) { + if (result) { + errorMsg = $("#errorMsg").html(); + $.ajax({ + type: "POST", + url: submitErrorReportURL, + data: AddAntiForgeryToken({ + Message: result, + Exception: errorMsg, + CurrentUrl: window.location.href + }), + success: function (response) { + if (response.result) { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html( + '
Thank you for your help! Feedback has been submitted.
'); + } else { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg") + .html( + '
' + + parseErrorMessage(response) + + '
'); + } + } + }); + } + } + }); + }); + + $(".view-details-button").on("click", + function () { + var link = $(this); + var linkText = link.text().toUpperCase(); + + if (linkText === "SHOW DETAILS") { + link.text("Hide Details"); + } else { + link.text("Show Details"); + }; + } + ); +}); diff --git a/IdentityServer/Scripts/signout-redirect.js b/IdentityServer/Scripts/signout-redirect.js new file mode 100644 index 0000000..cdfc5e7 --- /dev/null +++ b/IdentityServer/Scripts/signout-redirect.js @@ -0,0 +1,6 @@ +window.addEventListener("load", function () { + var a = document.querySelector("a.PostLogoutRedirectUri"); + if (a) { + window.location = a.href; + } +}); diff --git a/IdentityServer/Security/PasswordHasher.cs b/IdentityServer/Security/PasswordHasher.cs new file mode 100644 index 0000000..203f4e1 --- /dev/null +++ b/IdentityServer/Security/PasswordHasher.cs @@ -0,0 +1,62 @@ +using Microsoft.AspNetCore.Identity; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Teknik.Configuration; +using Microsoft.Extensions.Identity.Core; +using Microsoft.Extensions.Options; +using Teknik.Utilities.Cryptography; +using Teknik.Utilities; +using System.Text; +using Teknik.IdentityServer.Models; + +namespace Teknik.IdentityServer.Security +{ + public class TeknikPasswordHasher : PasswordHasher + { + private readonly Config _config; + + public TeknikPasswordHasher(Config config) + { + _config = config; + } + + public override PasswordVerificationResult VerifyHashedPassword(ApplicationUser user, string hashedPassword, string providedPassword) + { + if (hashedPassword == null) + { + throw new ArgumentNullException(nameof(hashedPassword)); + } + if (providedPassword == null) + { + throw new ArgumentNullException(nameof(providedPassword)); + } + + // Test legacy password hashes + #region Legacy Checks + byte[] hashBytes = SHA384.Hash(user.UserName, providedPassword); + string hash = hashBytes.ToHex(); + if (hashedPassword == hash) + { + return PasswordVerificationResult.SuccessRehashNeeded; + } + + hash = Encoding.ASCII.GetString(hashBytes); + if (hashedPassword == hash) + { + return PasswordVerificationResult.SuccessRehashNeeded; + } + + hash = SHA256.Hash(providedPassword, _config.Salt1, _config.Salt2); + if (hashedPassword == hash) + { + return PasswordVerificationResult.SuccessRehashNeeded; + } + #endregion + + // Test Latest + return base.VerifyHashedPassword(user, hashedPassword, providedPassword); + } + } +} diff --git a/IdentityServer/Security/SecurityHeadersAttribute.cs b/IdentityServer/Security/SecurityHeadersAttribute.cs new file mode 100644 index 0000000..b9b75cc --- /dev/null +++ b/IdentityServer/Security/SecurityHeadersAttribute.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Teknik.IdentityServer.Security +{ + public class SecurityHeadersAttribute : ActionFilterAttribute + { + public override void OnResultExecuting(ResultExecutingContext context) + { + var result = context.Result; + if (result is ViewResult) + { + if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Type-Options")) + { + context.HttpContext.Response.Headers.Add("X-Content-Type-Options", "nosniff"); + } + if (!context.HttpContext.Response.Headers.ContainsKey("X-Frame-Options")) + { + context.HttpContext.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); + } + + var csp = "default-src 'self';"; + // an example if you need client images to be displayed from twitter + //var csp = "default-src 'self'; img-src 'self' https://pbs.twimg.com"; + + // once for standards compliant browsers + if (!context.HttpContext.Response.Headers.ContainsKey("Content-Security-Policy")) + { + context.HttpContext.Response.Headers.Add("Content-Security-Policy", csp); + } + // and once again for IE + if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Security-Policy")) + { + context.HttpContext.Response.Headers.Add("X-Content-Security-Policy", csp); + } + } + } + } +} diff --git a/IdentityServer/Security/TeknikRedirectUriValidator.cs b/IdentityServer/Security/TeknikRedirectUriValidator.cs new file mode 100644 index 0000000..35ffc5e --- /dev/null +++ b/IdentityServer/Security/TeknikRedirectUriValidator.cs @@ -0,0 +1,60 @@ +using IdentityServer4.Models; +using IdentityServer4.Validation; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Teknik.Configuration; + +namespace Teknik.IdentityServer.Security +{ + public class TeknikRedirectUriValidator : IRedirectUriValidator + { + private readonly Config _config; + + public TeknikRedirectUriValidator(Config config) + { + _config = config; + } + + public async Task IsPostLogoutRedirectUriValidAsync(string requestedUri, Client client) + { + if (client.PostLogoutRedirectUris != null && client.PostLogoutRedirectUris.Any()) + { + if (client.PostLogoutRedirectUris.Contains(requestedUri)) + return true; + } + + // Add special case for pre-configured redirect URIs + if (client.ClientId == _config.UserConfig.IdentityServerConfig.ClientId && + _config.UserConfig.IdentityServerConfig.PostLogoutRedirectUris != null && + _config.UserConfig.IdentityServerConfig.PostLogoutRedirectUris.Any()) + { + if (_config.UserConfig.IdentityServerConfig.PostLogoutRedirectUris.Contains(requestedUri)) + return true; + } + + return false; + } + + public async Task IsRedirectUriValidAsync(string requestedUri, Client client) + { + if (client.RedirectUris != null && client.RedirectUris.Any()) + { + if (client.RedirectUris.Contains(requestedUri)) + return true; + } + + // Add special case for pre-configured redirect URIs + if (client.ClientId == _config.UserConfig.IdentityServerConfig.ClientId && + _config.UserConfig.IdentityServerConfig.RedirectUris != null && + _config.UserConfig.IdentityServerConfig.RedirectUris.Any()) + { + if (_config.UserConfig.IdentityServerConfig.RedirectUris.Contains(requestedUri)) + return true; + } + + return false; + } + } +} diff --git a/IdentityServer/Services/AccountService.cs b/IdentityServer/Services/AccountService.cs new file mode 100644 index 0000000..4469d0b --- /dev/null +++ b/IdentityServer/Services/AccountService.cs @@ -0,0 +1,107 @@ +using IdentityModel; +using IdentityServer4.Extensions; +using IdentityServer4.Services; +using IdentityServer4.Stores; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using System.Linq; +using System.Threading.Tasks; +using Teknik.IdentityServer.Models; +using Teknik.IdentityServer.Options; +using Teknik.IdentityServer.ViewModels; + +namespace Teknik.IdentityServer.Services +{ + public class AccountService + { + private readonly IClientStore _clientStore; + private readonly IIdentityServerInteractionService _interaction; + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IAuthenticationSchemeProvider _schemeProvider; + + public AccountService( + IIdentityServerInteractionService interaction, + IHttpContextAccessor httpContextAccessor, + IAuthenticationSchemeProvider schemeProvider, + IClientStore clientStore) + { + _interaction = interaction; + _httpContextAccessor = httpContextAccessor; + _schemeProvider = schemeProvider; + _clientStore = clientStore; + } + + public async Task BuildLoginViewModelAsync(string returnUrl) + { + var context = await _interaction.GetAuthorizationContextAsync(returnUrl); + + var allowLocal = true; + if (context?.ClientId != null) + { + var client = await _clientStore.FindEnabledClientByIdAsync(context.ClientId); + if (client != null) + { + allowLocal = client.EnableLocalLogin; + } + } + + return new LoginViewModel + { + AllowRememberLogin = AccountOptions.AllowRememberLogin, + EnableLocalLogin = allowLocal && AccountOptions.AllowLocalLogin, + ReturnUrl = returnUrl, + Username = context?.LoginHint + }; + } + + public async Task BuildLoginViewModelAsync(LoginInputModel model) + { + var vm = await BuildLoginViewModelAsync(model.ReturnUrl); + vm.Username = model.Username; + vm.RememberMe = model.RememberMe; + return vm; + } + + public async Task BuildLogoutViewModelAsync(string logoutId) + { + var vm = new LogoutViewModel { LogoutId = logoutId, ShowLogoutPrompt = AccountOptions.ShowLogoutPrompt }; + + var user = _httpContextAccessor.HttpContext.User; + if (user?.Identity.IsAuthenticated != true) + { + // if the user is not authenticated, then just show logged out page + vm.ShowLogoutPrompt = false; + return vm; + } + + var context = await _interaction.GetLogoutContextAsync(logoutId); + if (context?.ShowSignoutPrompt == false) + { + // it's safe to automatically sign-out + vm.ShowLogoutPrompt = false; + return vm; + } + + // show the logout prompt. this prevents attacks where the user + // is automatically signed out by another malicious web page. + return vm; + } + + public async Task BuildLoggedOutViewModelAsync(string logoutId) + { + // get context information (client name, post logout redirect URI and iframe for federated signout) + var logout = await _interaction.GetLogoutContextAsync(logoutId); + + var vm = new LoggedOutViewModel + { + AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut, + PostLogoutRedirectUri = logout?.PostLogoutRedirectUri, + ClientName = logout?.ClientId, + SignOutIframeUrl = logout?.SignOutIFrameUrl, + LogoutId = logoutId + }; + + return vm; + } + } +} diff --git a/IdentityServer/Services/ConsentService.cs b/IdentityServer/Services/ConsentService.cs new file mode 100644 index 0000000..312f48f --- /dev/null +++ b/IdentityServer/Services/ConsentService.cs @@ -0,0 +1,190 @@ +using IdentityServer4.Models; +using IdentityServer4.Services; +using IdentityServer4.Stores; +using Microsoft.Extensions.Logging; +using System.Linq; +using System.Threading.Tasks; +using Teknik.IdentityServer.Models; +using Teknik.IdentityServer.Options; +using Teknik.IdentityServer.ViewModels; + +namespace Teknik.IdentityServer.Services +{ + public class ConsentService + { + private readonly IClientStore _clientStore; + private readonly IResourceStore _resourceStore; + private readonly IIdentityServerInteractionService _interaction; + private readonly ILogger _logger; + + public ConsentService( + IIdentityServerInteractionService interaction, + IClientStore clientStore, + IResourceStore resourceStore, + ILogger logger) + { + _interaction = interaction; + _clientStore = clientStore; + _resourceStore = resourceStore; + _logger = logger; + } + + public async Task ProcessConsent(ConsentInputModel model) + { + var result = new ProcessConsentResult(); + + ConsentResponse grantedConsent = null; + + // user clicked 'no' - send back the standard 'access_denied' response + if (model.Button == "no") + { + grantedConsent = ConsentResponse.Denied; + } + // user clicked 'yes' - validate the data + else if (model.Button == "yes" && model != null) + { + // if the user consented to some scope, build the response model + if (model.ScopesConsented != null && model.ScopesConsented.Any()) + { + var scopes = model.ScopesConsented; + if (ConsentOptions.EnableOfflineAccess == false) + { + scopes = scopes.Where(x => x != IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess); + } + + grantedConsent = new ConsentResponse + { + RememberConsent = model.RememberConsent, + ScopesConsented = scopes.ToArray() + }; + } + else + { + result.ValidationError = ConsentOptions.MustChooseOneErrorMessage; + } + } + else + { + result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage; + } + + if (grantedConsent != null) + { + // validate return url is still valid + var request = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); + if (request == null) return result; + + // communicate outcome of consent back to identityserver + await _interaction.GrantConsentAsync(request, grantedConsent); + + // indicate that's it ok to redirect back to authorization endpoint + result.RedirectUri = model.ReturnUrl; + } + else + { + // we need to redisplay the consent UI + result.ViewModel = await BuildViewModelAsync(model.ReturnUrl, model); + } + + return result; + } + + public async Task BuildViewModelAsync(string returnUrl, ConsentInputModel model = null) + { + var request = await _interaction.GetAuthorizationContextAsync(returnUrl); + if (request != null) + { + var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId); + if (client != null) + { + var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested); + if (resources != null && (resources.IdentityResources.Any() || resources.ApiResources.Any())) + { + return CreateConsentViewModel(model, returnUrl, request, client, resources); + } + else + { + _logger.LogError("No scopes matching: {0}", request.ScopesRequested.Aggregate((x, y) => x + ", " + y)); + } + } + else + { + _logger.LogError("Invalid client id: {0}", request.ClientId); + } + } + else + { + _logger.LogError("No consent request matching request: {0}", returnUrl); + } + + return null; + } + + private ConsentViewModel CreateConsentViewModel( + ConsentInputModel model, string returnUrl, + AuthorizationRequest request, + Client client, Resources resources) + { + var vm = new ConsentViewModel(); + vm.RememberConsent = model?.RememberConsent ?? true; + vm.ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty(); + + vm.ReturnUrl = returnUrl; + + vm.ClientName = client.ClientName ?? client.ClientId; + vm.ClientUrl = client.ClientUri; + vm.ClientLogoUrl = client.LogoUri; + vm.AllowRememberConsent = client.AllowRememberConsent; + + vm.IdentityScopes = resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray(); + vm.ResourceScopes = resources.ApiResources.SelectMany(x => x.Scopes).Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray(); + if (ConsentOptions.EnableOfflineAccess && resources.OfflineAccess) + { + vm.ResourceScopes = vm.ResourceScopes.Union(new ScopeViewModel[] { + GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess) || model == null) + }); + } + + return vm; + } + + public ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) + { + return new ScopeViewModel + { + Name = identity.Name, + DisplayName = identity.DisplayName, + Description = identity.Description, + Emphasize = identity.Emphasize, + Required = identity.Required, + Checked = check || identity.Required + }; + } + + public ScopeViewModel CreateScopeViewModel(Scope scope, bool check) + { + return new ScopeViewModel + { + Name = scope.Name, + DisplayName = scope.DisplayName, + Description = scope.Description, + Emphasize = scope.Emphasize, + Required = scope.Required, + Checked = check || scope.Required + }; + } + + private ScopeViewModel GetOfflineAccessScope(bool check) + { + return new ScopeViewModel + { + Name = IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess, + DisplayName = ConsentOptions.OfflineAccessDisplayName, + Description = ConsentOptions.OfflineAccessDescription, + Emphasize = true, + Checked = check + }; + } + } +} + diff --git a/IdentityServer/Startup.cs b/IdentityServer/Startup.cs new file mode 100644 index 0000000..0cb5e38 --- /dev/null +++ b/IdentityServer/Startup.cs @@ -0,0 +1,245 @@ +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Security.Claims; +using IdentityServer4.EntityFramework.DbContexts; +using IdentityServer4.EntityFramework.Mappers; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; +using Teknik.Configuration; +using Teknik.IdentityServer.Configuration; +using Teknik.IdentityServer.Security; +using Teknik.IdentityServer.Middleware; +using Teknik.Logging; +using Microsoft.AspNetCore.Authorization; +using Teknik.IdentityServer.Models; +using IdentityServer4.Services; + +namespace Teknik.IdentityServer +{ + public class Startup + { + public Startup(IConfiguration configuration, IHostingEnvironment env) + { + Configuration = configuration; + Environment = env; + } + + public IConfiguration Configuration { get; } + public IHostingEnvironment Environment { get; } + + public void ConfigureServices(IServiceCollection services) + { + string dataDir = Configuration["ConfigDirectory"]; + AppDomain.CurrentDomain.SetData("DataDirectory", dataDir); + + var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name; + + // Create Configuration Singleton + services.AddScoped(opt => Config.Load(dataDir)); + + // Build an intermediate service provider + var sp = services.BuildServiceProvider(); + + // Resolve the services from the service provider + var config = sp.GetService(); + + services.ConfigureApplicationCookie(options => + { + options.Cookie.Name = "TeknikAuth"; + options.Cookie.SecurePolicy = CookieSecurePolicy.Always; + options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict; + options.Cookie.Expiration = TimeSpan.FromDays(30); + options.ExpireTimeSpan = TimeSpan.FromDays(30); + }); + + services.AddHttpsRedirection(options => + { + options.RedirectStatusCode = StatusCodes.Status301MovedPermanently; + }); + + // Sessions + services.AddResponseCaching(); + services.AddMemoryCache(); + services.AddSession(); + + // Set the anti-forgery cookie name + services.AddAntiforgery(options => + { + options.Cookie.Name = "TeknikAuthAntiForgery"; + options.Cookie.SecurePolicy = CookieSecurePolicy.Always; + options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict; + }); + + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + + services.AddDbContext(builder => + builder.UseSqlServer(Configuration.GetConnectionString("TeknikAuthEntities"), sqlOptions => sqlOptions.MigrationsAssembly(migrationsAssembly))); + + services.AddIdentity(options => + { + options.Password = new PasswordOptions() + { + RequireDigit = false, + RequiredLength = 4, + RequiredUniqueChars = 1, + RequireLowercase = false, + RequireNonAlphanumeric = false, + RequireUppercase = false + }; + }) + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + + services.AddIdentityServer(options => + { + options.Events.RaiseErrorEvents = true; + options.Events.RaiseInformationEvents = true; + options.Events.RaiseFailureEvents = true; + options.Events.RaiseSuccessEvents = true; + options.UserInteraction.ErrorUrl = "/Error/IdentityError"; + options.UserInteraction.ErrorIdParameter = "errorId"; + }) + .AddOperationalStore(options => + options.ConfigureDbContext = builder => + builder.UseSqlServer(Configuration.GetConnectionString("TeknikAuthEntities"), sqlOptions => sqlOptions.MigrationsAssembly(migrationsAssembly))) + .AddConfigurationStore(options => + options.ConfigureDbContext = builder => + builder.UseSqlServer(Configuration.GetConnectionString("TeknikAuthEntities"), sqlOptions => sqlOptions.MigrationsAssembly(migrationsAssembly))) + .AddAspNetIdentity() + .AddRedirectUriValidator() + .AddDeveloperSigningCredential(); + + services.AddAuthorization(options => + { + foreach (var policy in Policies.Get()) + { + options.AddPolicy(policy.Name, p => + { + foreach (var scope in policy.Scopes) + { + p.RequireScope(scope); + } + }); + } + }); + + services.AddAuthentication("Bearer") + .AddIdentityServerAuthentication(options => + { + options.Authority = config.UserConfig.IdentityServerConfig.Authority; + options.RequireHttpsMetadata = true; + + options.ApiName = "auth-api"; + }); + + services.AddTransient, TeknikPasswordHasher>(); + services.AddTransient(); + } + + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, Config config) + { + // Initiate Logging + loggerFactory.AddLogger(config); + + // Setup the HttpContext + app.UseHttpContextSetup(); + + // HttpContext Session + app.UseSession(new SessionOptions() + { + IdleTimeout = TimeSpan.FromMinutes(30), + Cookie = new CookieBuilder() + { + Domain = null, + Name = "TeknikAuthSession", + SecurePolicy = CookieSecurePolicy.Always, + SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict + } + }); + + // Use Exception Handling + app.UseErrorHandler(config); + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + // Custom Middleware + app.UseBlacklist(); + app.UseCORS(); + app.UseCSP(); + app.UseSecurityHeaders(); + + // Cache Responses + app.UseResponseCaching(); + + // Force a HTTPS redirection (301) + app.UseHttpsRedirection(); + + // Setup static files anc cache them client side + app.UseStaticFiles(new StaticFileOptions + { + OnPrepareResponse = ctx => + { + ctx.Context.Response.Headers[HeaderNames.CacheControl] = "public,max-age=" + 31536000; + } + }); + + InitializeDbTestDataAsync(app, config).Wait(); + + app.UseIdentityServer(); + + app.UseMvcWithDefaultRoute(); + } + + private static async System.Threading.Tasks.Task InitializeDbTestDataAsync(IApplicationBuilder app, Config config) + { + using (var scope = app.ApplicationServices.GetService().CreateScope()) + { + scope.ServiceProvider.GetRequiredService().Database.Migrate(); + scope.ServiceProvider.GetRequiredService().Database.Migrate(); + scope.ServiceProvider.GetRequiredService().Database.Migrate(); + + var context = scope.ServiceProvider.GetRequiredService(); + + if (!context.Clients.Any()) + { + foreach (var client in Clients.Get(config)) + { + context.Clients.Add(client.ToEntity()); + } + context.SaveChanges(); + } + + if (!context.IdentityResources.Any()) + { + foreach (var resource in Resources.GetIdentityResources()) + { + context.IdentityResources.Add(resource.ToEntity()); + } + context.SaveChanges(); + } + + if (!context.ApiResources.Any()) + { + foreach (var resource in Resources.GetApiResources(config)) + { + context.ApiResources.Add(resource.ToEntity()); + } + context.SaveChanges(); + } + } + } + } +} diff --git a/IdentityServer/TeknikProfileService.cs b/IdentityServer/TeknikProfileService.cs new file mode 100644 index 0000000..8df19c1 --- /dev/null +++ b/IdentityServer/TeknikProfileService.cs @@ -0,0 +1,49 @@ +using IdentityModel; +using IdentityServer4.Extensions; +using IdentityServer4.Models; +using IdentityServer4.Services; +using Microsoft.AspNetCore.Identity; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Teknik.IdentityServer.Models; +using Teknik.Utilities; + +namespace Teknik.IdentityServer +{ + public class TeknikProfileService : IProfileService + { + private readonly IUserClaimsPrincipalFactory _claimsFactory; + private readonly UserManager _userManager; + + public TeknikProfileService(UserManager userManager, IUserClaimsPrincipalFactory claimsFactory) + { + _userManager = userManager; + _claimsFactory = claimsFactory; + } + + public async Task GetProfileDataAsync(ProfileDataRequestContext context) + { + var sub = context.Subject.GetSubjectId(); + var user = await _userManager.FindByIdAsync(sub); + var principal = await _claimsFactory.CreateAsync(user); + + var claims = principal.Claims.ToList(); + + claims.AddRange(user.ToClaims()); + + claims = claims.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList(); + + context.IssuedClaims = claims; + } + + public async Task IsActiveAsync(IsActiveContext context) + { + var sub = context.Subject.GetSubjectId(); + var user = await _userManager.FindByIdAsync(sub); + context.IsActive = user != null && user.AccountStatus == Utilities.AccountStatus.Active; + } + } +} diff --git a/IdentityServer/ViewModels/ConsentViewModel.cs b/IdentityServer/ViewModels/ConsentViewModel.cs new file mode 100644 index 0000000..85b2dbc --- /dev/null +++ b/IdentityServer/ViewModels/ConsentViewModel.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using Teknik.IdentityServer.Models; + +namespace Teknik.IdentityServer.ViewModels +{ + public class ConsentViewModel : ConsentInputModel + { + public string ClientName { get; set; } + public string ClientUrl { get; set; } + public string ClientLogoUrl { get; set; } + public bool AllowRememberConsent { get; set; } + + public IEnumerable IdentityScopes { get; set; } + public IEnumerable ResourceScopes { get; set; } + } +} diff --git a/IdentityServer/ViewModels/ErrorViewModel.cs b/IdentityServer/ViewModels/ErrorViewModel.cs new file mode 100644 index 0000000..4beffa9 --- /dev/null +++ b/IdentityServer/ViewModels/ErrorViewModel.cs @@ -0,0 +1,12 @@ +using System; + +namespace Teknik.IdentityServer.ViewModels +{ + public class ErrorViewModel + { + public string Title { get; set; } + public string Description { get; set; } + public int StatusCode { get; set; } + public Exception Exception { get; set; } + } +} \ No newline at end of file diff --git a/IdentityServer/ViewModels/GrantsViewModel.cs b/IdentityServer/ViewModels/GrantsViewModel.cs new file mode 100644 index 0000000..34643b6 --- /dev/null +++ b/IdentityServer/ViewModels/GrantsViewModel.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; + +namespace Teknik.IdentityServer.ViewModels +{ + public class GrantsViewModel + { + public IEnumerable Grants { get; set; } + } + + public class GrantViewModel + { + public string ClientId { get; set; } + public string ClientName { get; set; } + public string ClientUrl { get; set; } + public string ClientLogoUrl { get; set; } + public DateTime Created { get; set; } + public DateTime? Expires { get; set; } + public IEnumerable IdentityGrantNames { get; set; } + public IEnumerable ApiGrantNames { get; set; } + } +} diff --git a/IdentityServer/ViewModels/IdentityErrorViewModel.cs b/IdentityServer/ViewModels/IdentityErrorViewModel.cs new file mode 100644 index 0000000..e309af7 --- /dev/null +++ b/IdentityServer/ViewModels/IdentityErrorViewModel.cs @@ -0,0 +1,10 @@ +using System; + +namespace Teknik.IdentityServer.ViewModels +{ + public class IdentityErrorViewModel + { + public string Title { get; set; } + public string Description { get; set; } + } +} \ No newline at end of file diff --git a/IdentityServer/ViewModels/LoggedOutViewModel.cs b/IdentityServer/ViewModels/LoggedOutViewModel.cs new file mode 100644 index 0000000..9db7cb5 --- /dev/null +++ b/IdentityServer/ViewModels/LoggedOutViewModel.cs @@ -0,0 +1,13 @@ +namespace Teknik.IdentityServer.ViewModels +{ + public class LoggedOutViewModel + { + public string PostLogoutRedirectUri { get; set; } + public string ClientName { get; set; } + public string SignOutIframeUrl { get; set; } + + public bool AutomaticRedirectAfterSignOut { get; set; } + + public string LogoutId { get; set; } + } +} \ No newline at end of file diff --git a/IdentityServer/ViewModels/LoginViewModel.cs b/IdentityServer/ViewModels/LoginViewModel.cs new file mode 100644 index 0000000..d80504a --- /dev/null +++ b/IdentityServer/ViewModels/LoginViewModel.cs @@ -0,0 +1,10 @@ +using Teknik.IdentityServer.Models; + +namespace Teknik.IdentityServer.ViewModels +{ + public class LoginViewModel : LoginInputModel + { + public bool AllowRememberLogin { get; set; } + public bool EnableLocalLogin { get; set; } + } +} diff --git a/IdentityServer/ViewModels/LoginWith2faViewModel.cs b/IdentityServer/ViewModels/LoginWith2faViewModel.cs new file mode 100644 index 0000000..2af28f8 --- /dev/null +++ b/IdentityServer/ViewModels/LoginWith2faViewModel.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; + +namespace Teknik.IdentityServer.ViewModels +{ + public class LoginWith2faViewModel + { + [Required] + [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Text)] + [Display(Name = "Authenticator code")] + public string TwoFactorCode { get; set; } + + [Display(Name = "Remember this machine")] + public bool RememberMachine { get; set; } + + public bool RememberMe { get; set; } + } +} diff --git a/IdentityServer/ViewModels/LoginWithRecoveryCodeViewModel.cs b/IdentityServer/ViewModels/LoginWithRecoveryCodeViewModel.cs new file mode 100644 index 0000000..2a0e458 --- /dev/null +++ b/IdentityServer/ViewModels/LoginWithRecoveryCodeViewModel.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace Teknik.IdentityServer.ViewModels +{ + public class LoginWithRecoveryCodeViewModel + { + [Required] + [DataType(DataType.Text)] + [Display(Name = "Recovery Code")] + public string RecoveryCode { get; set; } + } +} diff --git a/IdentityServer/ViewModels/LogoutViewModel.cs b/IdentityServer/ViewModels/LogoutViewModel.cs new file mode 100644 index 0000000..73a7011 --- /dev/null +++ b/IdentityServer/ViewModels/LogoutViewModel.cs @@ -0,0 +1,9 @@ +using Teknik.IdentityServer.Models; + +namespace Teknik.IdentityServer.ViewModels +{ + public class LogoutViewModel : LogoutInputModel + { + public bool ShowLogoutPrompt { get; set; } + } +} diff --git a/IdentityServer/ViewModels/ScopeViewModel.cs b/IdentityServer/ViewModels/ScopeViewModel.cs new file mode 100644 index 0000000..ada9558 --- /dev/null +++ b/IdentityServer/ViewModels/ScopeViewModel.cs @@ -0,0 +1,12 @@ +namespace Teknik.IdentityServer.ViewModels +{ + public class ScopeViewModel + { + public string Name { get; set; } + public string DisplayName { get; set; } + public string Description { get; set; } + public bool Emphasize { get; set; } + public bool Required { get; set; } + public bool Checked { get; set; } + } +} diff --git a/IdentityServer/ViewModels/SubmitReportViewModel.cs b/IdentityServer/ViewModels/SubmitReportViewModel.cs new file mode 100644 index 0000000..f411407 --- /dev/null +++ b/IdentityServer/ViewModels/SubmitReportViewModel.cs @@ -0,0 +1,11 @@ +namespace Teknik.IdentityServer.ViewModels +{ + public class SubmitReportViewModel + { + public string Message { get; set; } + + public string Exception { get; set; } + + public string CurrentUrl { get; set; } + } +} diff --git a/IdentityServer/ViewModels/ViewModelBase.cs b/IdentityServer/ViewModels/ViewModelBase.cs new file mode 100644 index 0000000..b0f2e58 --- /dev/null +++ b/IdentityServer/ViewModels/ViewModelBase.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Teknik.IdentityServer.ViewModels +{ + public class ViewModelBase + { + public bool Error { get; set; } + + public string ErrorMessage { get; set; } + + public ViewModelBase() + { + Error = false; + ErrorMessage = string.Empty; + } + } +} diff --git a/IdentityServer/Views/Account/AccessDenied.cshtml b/IdentityServer/Views/Account/AccessDenied.cshtml new file mode 100644 index 0000000..8a3370d --- /dev/null +++ b/IdentityServer/Views/Account/AccessDenied.cshtml @@ -0,0 +1,14 @@ +@{ + ViewData["Title"] = "Access denied"; +} + +
+
+
+
+

@ViewData["Title"]

+

You do not have access to this resource.

+
+
+
+
diff --git a/IdentityServer/Views/Account/Banned.cshtml b/IdentityServer/Views/Account/Banned.cshtml new file mode 100644 index 0000000..67d628b --- /dev/null +++ b/IdentityServer/Views/Account/Banned.cshtml @@ -0,0 +1,14 @@ +@{ + ViewData["Title"] = "Account Banned"; +} + +
+
+
+
+

@ViewData["Title"]

+

This account has been banned.

+
+
+
+
\ No newline at end of file diff --git a/IdentityServer/Views/Account/Lockout.cshtml b/IdentityServer/Views/Account/Lockout.cshtml new file mode 100644 index 0000000..f573a1f --- /dev/null +++ b/IdentityServer/Views/Account/Lockout.cshtml @@ -0,0 +1,8 @@ +@{ + ViewData["Title"] = "Locked out"; +} + +
+

@ViewData["Title"]

+

This account has been locked out, please try again later.

+
diff --git a/IdentityServer/Views/Account/LoggedOut.cshtml b/IdentityServer/Views/Account/LoggedOut.cshtml new file mode 100644 index 0000000..4060c2a --- /dev/null +++ b/IdentityServer/Views/Account/LoggedOut.cshtml @@ -0,0 +1,35 @@ +@model LoggedOutViewModel + +@{ + // set this so the layout rendering sees an anonymous user + ViewData["signed-out"] = true; +} + +
+
+
+

+ Logout + You are now logged out +

+ + @if (Model.PostLogoutRedirectUri != null) + { +
+ Click here to return to the + @Model.ClientName application. +
+ } + + @if (Model.SignOutIframeUrl != null) + { + + } +
+
+
+ +@if (Model.AutomaticRedirectAfterSignOut) +{ + +} diff --git a/IdentityServer/Views/Account/Login.cshtml b/IdentityServer/Views/Account/Login.cshtml new file mode 100644 index 0000000..4be96ae --- /dev/null +++ b/IdentityServer/Views/Account/Login.cshtml @@ -0,0 +1,61 @@ +@model LoginViewModel + +@{ + string logoPath = "/images/logo-blue.svg"; + + // If we are using a CDN, let's replace it + if (Config.UseCdn) + { + if (!string.IsNullOrEmpty(Config.CdnHost)) + { + logoPath = Config.CdnHost.TrimEnd('/') + logoPath; + } + } +} + +@if (Config.UserConfig.LoginEnabled) +{ +
+
+
+
+ Teknik +
+
+
+ + @await Html.PartialAsync("_ValidationSummary") + + + @if (Model.EnableLocalLogin) + { +
+ +
+ +
+
+ +
+ @if (Model.AllowRememberLogin) + { +
+ + +
+ } +
+ + +
+
+ } +
+
+
+
+} +else +{ +

Authentication is currently disabled.

+} \ No newline at end of file diff --git a/IdentityServer/Views/Account/LoginWith2fa.cshtml b/IdentityServer/Views/Account/LoginWith2fa.cshtml new file mode 100644 index 0000000..f48fef8 --- /dev/null +++ b/IdentityServer/Views/Account/LoginWith2fa.cshtml @@ -0,0 +1,46 @@ +@model LoginWith2faViewModel + +
+
+
+
+
+

Two-factor authentication

+
+

Your login is protected with an authenticator app. Enter your authenticator code below.

+
+
+
+
+
+ +
+
+ + + +
+
+
+ + +
+
+
+ +
+
+
+
+
+
+
+

+ Don't have access to your authenticator device? You can + log in with a recovery code. +

+
+
+
+
+
diff --git a/IdentityServer/Views/Account/LoginWithRecoveryCode.cshtml b/IdentityServer/Views/Account/LoginWithRecoveryCode.cshtml new file mode 100644 index 0000000..1c6e14f --- /dev/null +++ b/IdentityServer/Views/Account/LoginWithRecoveryCode.cshtml @@ -0,0 +1,31 @@ +@model LoginWithRecoveryCodeViewModel + +
+
+
+
+
+

Recovery code verification

+
+

+ You have requested to login with a recovery code. This login will not be remembered until you provide + an authenticator app code at login or disable 2FA and login again. +

+
+
+
+
+
+
+
+ + + +
+ +
+
+
+
+
+
diff --git a/IdentityServer/Views/Account/Logout.cshtml b/IdentityServer/Views/Account/Logout.cshtml new file mode 100644 index 0000000..9992f2c --- /dev/null +++ b/IdentityServer/Views/Account/Logout.cshtml @@ -0,0 +1,23 @@ +@model LogoutViewModel + +
+ + +
+
+

Would you like to logout of IdentityServer?

+
+ +
+
+ +
+
+
+
+
+
+ + \ No newline at end of file diff --git a/IdentityServer/Views/Consent/Index.cshtml b/IdentityServer/Views/Consent/Index.cshtml new file mode 100644 index 0000000..b3a2f64 --- /dev/null +++ b/IdentityServer/Views/Consent/Index.cshtml @@ -0,0 +1,82 @@ +@model ConsentViewModel + + \ No newline at end of file diff --git a/IdentityServer/Views/Consent/_ScopeListItem.cshtml b/IdentityServer/Views/Consent/_ScopeListItem.cshtml new file mode 100644 index 0000000..e3fa22d --- /dev/null +++ b/IdentityServer/Views/Consent/_ScopeListItem.cshtml @@ -0,0 +1,34 @@ +@model ScopeViewModel + +
  • + + @if (Model.Required) + { + (required) + } + @if (Model.Description != null) + { + + } +
  • \ No newline at end of file diff --git a/IdentityServer/Views/Error/Exception.cshtml b/IdentityServer/Views/Error/Exception.cshtml new file mode 100644 index 0000000..e94e241 --- /dev/null +++ b/IdentityServer/Views/Error/Exception.cshtml @@ -0,0 +1,47 @@ +@model Teknik.IdentityServer.ViewModels.ErrorViewModel + + + +
    +
    +
    +
    +

    Unexpected Error

    +
    +

    Uh oh! You aren't supposed to be seeing this. Something went horribly wrong.

    +
    + @if (Model != null && Model.Exception != null) + { +
    + + Submit Bug Report +
    + } +
    + @if (Model != null && Model.Exception != null) + { +
    +
    + +
    +
    +
    +

    + Exception: @Model.Exception.GetFullMessage(true) +
    + Source: @Model.Exception.Source +

    +
    +
    @Model.Exception.StackTrace
    +
    +
    + } +
    +
    +
    +
    +
    + + diff --git a/IdentityServer/Views/Error/Http401.cshtml b/IdentityServer/Views/Error/Http401.cshtml new file mode 100644 index 0000000..46d2831 --- /dev/null +++ b/IdentityServer/Views/Error/Http401.cshtml @@ -0,0 +1,15 @@ +@model Teknik.IdentityServer.ViewModels.ErrorViewModel + +
    +
    +
    +
    +

    401 Unauthorized

    +
    + The request has not been applied because it lacks valid authentication credentials for the target resource. +
    +
    +
    +
    +
    + diff --git a/IdentityServer/Views/Error/Http403.cshtml b/IdentityServer/Views/Error/Http403.cshtml new file mode 100644 index 0000000..28d979c --- /dev/null +++ b/IdentityServer/Views/Error/Http403.cshtml @@ -0,0 +1,16 @@ +@model Teknik.IdentityServer.ViewModels.ErrorViewModel + +
    +
    +
    +
    +

    Sorry Bud!

    +

    403 Access Denied

    +
    + You do not have access to this resource. Please contact an Administrator if you think this is an error. +
    +
    +
    +
    +
    + diff --git a/IdentityServer/Views/Error/Http404.cshtml b/IdentityServer/Views/Error/Http404.cshtml new file mode 100644 index 0000000..0fdfeca --- /dev/null +++ b/IdentityServer/Views/Error/Http404.cshtml @@ -0,0 +1,16 @@ +@model Teknik.IdentityServer.ViewModels.ErrorViewModel + +
    +
    +
    +
    +

    Uh Oh!

    +

    404 Not Found

    +
    + Unable to find the resource you are looking for. Please contact an Administrator if you think this is in error. +
    +
    +
    +
    +
    + diff --git a/IdentityServer/Views/Error/Http500.cshtml b/IdentityServer/Views/Error/Http500.cshtml new file mode 100644 index 0000000..fbd3513 --- /dev/null +++ b/IdentityServer/Views/Error/Http500.cshtml @@ -0,0 +1,48 @@ +@model Teknik.IdentityServer.ViewModels.ErrorViewModel + + + +
    +
    +
    +
    +

    Whoops!

    +

    500 Server Error

    +
    +

    Sorry, a server error occured. Please contact an Administrator about this error.

    +
    + @if (Model != null && Model.Exception != null) + { +
    + + Submit Bug Report +
    + } +
    + @if (Model != null && Model.Exception != null) + { +
    +
    + +
    +
    +
    +

    + Exception: @Model.Exception.GetFullMessage(true) +
    + Source: @Model.Exception.Source +

    +
    +
    @Model.Exception.StackTrace
    +
    +
    + } +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/IdentityServer/Views/Error/HttpGeneral.cshtml b/IdentityServer/Views/Error/HttpGeneral.cshtml new file mode 100644 index 0000000..a9749fa --- /dev/null +++ b/IdentityServer/Views/Error/HttpGeneral.cshtml @@ -0,0 +1,16 @@ +@model Teknik.IdentityServer.ViewModels.ErrorViewModel + +
    +
    +
    +
    +

    Http Error

    +

    Status Code: @Model.StatusCode

    +
    + Sorry, an error has occured: @Model.Description +
    +
    +
    +
    +
    + diff --git a/IdentityServer/Views/Error/IdentityError.cshtml b/IdentityServer/Views/Error/IdentityError.cshtml new file mode 100644 index 0000000..ab21204 --- /dev/null +++ b/IdentityServer/Views/Error/IdentityError.cshtml @@ -0,0 +1,15 @@ +@model Teknik.IdentityServer.ViewModels.IdentityErrorViewModel + +
    +
    +
    +
    +

    Identity Error Occured

    +

    @Model.Title

    +
    +

    @Model.Description

    +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/IdentityServer/Views/Grants/Index.cshtml b/IdentityServer/Views/Grants/Index.cshtml new file mode 100644 index 0000000..c1e1009 --- /dev/null +++ b/IdentityServer/Views/Grants/Index.cshtml @@ -0,0 +1,79 @@ +@model GrantsViewModel + +
    + + + @if (Model.Grants.Any() == false) + { +
    +
    +
    + You have not given access to any applications +
    +
    +
    + } + else + { + foreach (var grant in Model.Grants) + { +
    +
    + @if (grant.ClientLogoUrl != null) + { + + } +
    +
    +
    @grant.ClientName
    +
    + Created: @grant.Created.ToString("yyyy-MM-dd") +
    + @if (grant.Expires.HasValue) + { +
    + Expires: @grant.Expires.Value.ToString("yyyy-MM-dd") +
    + } + @if (grant.IdentityGrantNames.Any()) + { +
    +
    Identity Grants
    +
      + @foreach (var name in grant.IdentityGrantNames) + { +
    • @name
    • + } +
    +
    + } + @if (grant.ApiGrantNames.Any()) + { +
    +
    API Grants
    +
      + @foreach (var name in grant.ApiGrantNames) + { +
    • @name
    • + } +
    +
    + } +
    +
    +
    + + +
    +
    +
    + } + } +
    \ No newline at end of file diff --git a/IdentityServer/Views/Home/Index.cshtml b/IdentityServer/Views/Home/Index.cshtml new file mode 100644 index 0000000..13cd531 --- /dev/null +++ b/IdentityServer/Views/Home/Index.cshtml @@ -0,0 +1,32 @@ +@model ViewModelBase + +@{ + string logoPath = "/images/logo-blue.svg"; + + // If we are using a CDN, let's replace it + if (Config.UseCdn) + { + if (!string.IsNullOrEmpty(Config.CdnHost)) + { + logoPath = Config.CdnHost.TrimEnd('/') + logoPath; + } + } +} + +
    +
    +
    +
    + Teknik +
    +
    +
    +

    Welcome to the Teknik Identity Server

    +

    + You can view our discovery document + where you can find metadata and links to all the endpoints, key material, etc. +

    +
    +
    +
    +
    \ No newline at end of file diff --git a/IdentityServer/Views/Shared/_Layout.cshtml b/IdentityServer/Views/Shared/_Layout.cshtml new file mode 100644 index 0000000..858b874 --- /dev/null +++ b/IdentityServer/Views/Shared/_Layout.cshtml @@ -0,0 +1,46 @@ +@using IdentityServer4.Extensions +@{ + string name = null; + if (!true.Equals(ViewData["signed-out"])) + { + name = Context.User?.GetDisplayName(); + } +} + + + + + + + + + + + + + + + + + + + + + + + + @ViewBag.Title + + + + + + + + + +
    + @RenderBody() +
    + + diff --git a/IdentityServer/Views/Shared/_ValidationSummary.cshtml b/IdentityServer/Views/Shared/_ValidationSummary.cshtml new file mode 100644 index 0000000..ed77ee5 --- /dev/null +++ b/IdentityServer/Views/Shared/_ValidationSummary.cshtml @@ -0,0 +1,6 @@ +@if (ViewContext.ModelState.IsValid == false) +{ +
    +
    +
    +} \ No newline at end of file diff --git a/IdentityServer/_ViewImports.cshtml b/IdentityServer/_ViewImports.cshtml new file mode 100644 index 0000000..d9b51d8 --- /dev/null +++ b/IdentityServer/_ViewImports.cshtml @@ -0,0 +1,11 @@ +@using Teknik.IdentityServer.ViewModels + +@using Teknik.Configuration +@using Teknik.Utilities +@using Teknik.Utilities.TagHelpers + +@inject Microsoft.AspNetCore.Hosting.IHostingEnvironment HostingEnvironment +@inject Config Config + +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper "*, Teknik.Utilities" diff --git a/IdentityServer/_ViewStart.cshtml b/IdentityServer/_ViewStart.cshtml new file mode 100644 index 0000000..2de6241 --- /dev/null +++ b/IdentityServer/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "~/Views/Shared/_Layout.cshtml"; +} diff --git a/IdentityServer/appsettings.json b/IdentityServer/appsettings.json new file mode 100644 index 0000000..068737d --- /dev/null +++ b/IdentityServer/appsettings.json @@ -0,0 +1,11 @@ +{ + "ConnectionStrings": { + "TeknikAuthEntities": "Server=(localdb)\\mssqllocaldb;Database=aspnet-TeknikCore-BE9563D1-0DFB-4141-903C-287B23FF22C7;Trusted_Connection=True;MultipleActiveResultSets=true" + }, + "server.urls": "http://localhost:5060", + "Logging": { + "LogLevel": { + "Default": "Warning" + } + } +} diff --git a/IdentityServer/bundleconfig.json b/IdentityServer/bundleconfig.json new file mode 100644 index 0000000..e0b7d04 --- /dev/null +++ b/IdentityServer/bundleconfig.json @@ -0,0 +1,32 @@ +[ + { + "outputFileName": "./wwwroot/js/common.min.js", + "inputFiles": [ + "./wwwroot/lib/jquery/js/jquery.js", + "./wwwroot/lib/jquery/js/jquery.validate.js", + "./wwwroot/lib/bootstrap/js/bootstrap.js", + "./wwwroot/js/app/common.js" + ] + }, + { + "outputFileName": "./wwwroot/css/common.min.css", + "inputFiles": [ + "./wwwroot/lib/bootstrap/css/bootstrap.css", + "./wwwroot/lib/awesome-bootstrap-checkbox/css/awesome-bootstrap-checkbox.css", + "./wwwroot/lib/font-awesome/css/font-awesome.css", + "./wwwroot/css/app/common.css" + ] + }, + { + "outputFileName": "./wwwroot/js/signout-redirect.min.js", + "inputFiles": [ + "./wwwroot/js/app/signout-redirect.js" + ] + }, + { + "outputFileName": "./wwwroot/js/error.min.js", + "inputFiles": [ + "./wwwroot/js/app/Error.js" + ] + } +] diff --git a/IdentityServer/gulpfile.js b/IdentityServer/gulpfile.js new file mode 100644 index 0000000..64ea64b --- /dev/null +++ b/IdentityServer/gulpfile.js @@ -0,0 +1,128 @@ +/// +"use strict"; + +var gulp = require('gulp'); +var rimraf = require("rimraf"); +var concat = require("gulp-concat"); +var cssmin = require("gulp-cssmin"); +var merge = require('merge-stream'); +var del = require("del"); +var replace = require("gulp-replace"); +var rename = require("gulp-rename"); +var git = require("git-rev-sync"); + +var uglifyes = require('uglify-es'); +var composer = require('gulp-uglify/composer'); +var uglify = composer(uglifyes, console); + +var bundleconfig = require("./bundleconfig.json"); + +var regex = { + css: /\.css$/, + html: /\.(html|htm)$/, + js: /\.js$/ +}; + +var assets = [ + // Library JS Files + { './node_modules/bootstrap/dist/js/bootstrap.js': 'lib/bootstrap/js' }, + { './node_modules/jquery/dist/jquery.js': 'lib/jquery/js' }, + { './node_modules/jquery-validation/dist/jquery.validate.js': 'lib/jquery/js' }, + + // App JS Files + { './Scripts/**/*': 'js/app' }, + + // Library CSS Files + { './node_modules/bootstrap/dist/css/bootstrap.css': 'lib/bootstrap/css' }, + { './node_modules/bootstrap/dist/css/bootstrap.css.map': 'lib/bootstrap/css' }, + { './node_modules/awesome-bootstrap-checkbox/awesome-bootstrap-checkbox.css': 'lib/awesome-bootstrap-checkbox/css' }, + { './node_modules/font-awesome/css/font-awesome.css': 'lib/font-awesome/css' }, + + // App CSS Files + { './Content/**/*': 'css/app' }, + + // Fonts + { './node_modules/bootstrap/dist/fonts/*': 'lib/bootstrap/fonts' }, + { './node_modules/bootstrap/dist/fonts/*': 'fonts' }, + { './node_modules/font-awesome/fonts/*': 'lib/font-awesome/fonts' }, + { './node_modules/font-awesome/fonts/*': 'fonts' }, + + // Images + { './Images/favicon.ico': 'images' }, + { './Images/logo-black.svg': 'images' }, + { './Images/logo-blue.svg': 'images' } +]; + +gulp.task("clean", function (cb) { + return rimraf("./wwwroot/*", cb); +}); + +gulp.task('copy-assets', function () { + var streams = []; + for (var asset in assets) { + for (var item in assets[asset]) { + streams.push(gulp.src(item).pipe(gulp.dest('./wwwroot/' + assets[asset][item]))); + } + } +}); + +gulp.task("load-bundle", function () { + bundleconfig = require("./bundleconfig.json"); +}); + +gulp.task("min", ["min:js", "min:css"]); + +gulp.task("min:js", function () { + var tasks = getBundles(".js").map(function (bundle) { + return gulp.src(bundle.inputFiles, { base: "." }) + .pipe(concat(bundle.outputFileName)) + .pipe(uglify()) + .pipe(gulp.dest(".")); + }); + return merge(tasks); +}); + +gulp.task("min:css", function () { + var tasks = getBundles(".css").map(function (bundle) { + return gulp.src(bundle.inputFiles, { base: "." }) + .pipe(concat(bundle.outputFileName)) + .pipe(cssmin()) + .pipe(gulp.dest(".")); + }); + return merge(tasks); +}); + +gulp.task("update-version", function () { + return gulp.src('./App_Data/version.template.json') + .pipe(replace('{{git_ver}}', git.tag())) + .pipe(replace('{{git_hash}}', git.long())) + .pipe(rename('version.json')) + .pipe(gulp.dest('./App_Data')); +}); + +gulp.task("watch", function () { + // Watch Source Files + assets.forEach(function (src) { + for (var key in src) { + gulp.watch(key, ["copy-assets"]); + } + }); + + // Watch Bundle File Itself + gulp.watch('./bundleconfig.json', ["load-bundle", "min"]); + + // Watch Bundles + getBundles(".js").forEach(function (bundle) { + gulp.watch(bundle.inputFiles, ["min:js"]); + }); + + getBundles(".css").forEach(function (bundle) { + gulp.watch(bundle.inputFiles, ["min:css"]); + }); +}); + +function getBundles(extension) { + return bundleconfig.filter(function (bundle) { + return new RegExp(`${extension}$`).test(bundle.outputFileName); + }); +} \ No newline at end of file diff --git a/IdentityServer/package-lock.json b/IdentityServer/package-lock.json new file mode 100644 index 0000000..b9e094d --- /dev/null +++ b/IdentityServer/package-lock.json @@ -0,0 +1,3528 @@ +{ + "name": "teknik", + "version": "2.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "dev": true + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", + "dev": true + }, + "array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "1.0.3" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "awesome-bootstrap-checkbox": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/awesome-bootstrap-checkbox/-/awesome-bootstrap-checkbox-1.0.1.tgz", + "integrity": "sha1-2rEBRrYAESmrCg7B5Uu3fGwwRXo=" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "1.0.1", + "class-utils": "0.3.6", + "component-emitter": "1.2.1", + "define-property": "1.0.0", + "isobject": "3.0.1", + "mixin-deep": "1.3.1", + "pascalcase": "0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + } + } + }, + "beeper": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", + "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", + "dev": true + }, + "binaryextensions": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.1.1.tgz", + "integrity": "sha512-XBaoWE9RW8pPdPQNibZsW2zh8TW6gcarXp1FZPwT8Uop8ScSNldJEWf2k9l3HeTqdrEwsOsFcq74RiJECW34yA==", + "dev": true + }, + "bootstrap": { + "version": "3.3.7", + "resolved": "http://registry.npmjs.org/bootstrap/-/bootstrap-3.3.7.tgz", + "integrity": "sha1-WjiTlFSfIzMIdaOxUGVldPip63E=" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.3", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "1.0.0", + "component-emitter": "1.2.1", + "get-value": "2.0.6", + "has-value": "1.0.0", + "isobject": "3.0.1", + "set-value": "2.0.0", + "to-object-path": "0.3.0", + "union-value": "1.0.0", + "unset-value": "1.0.0" + } + }, + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "2.1.1", + "map-obj": "1.0.1" + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "3.1.0", + "define-property": "0.2.5", + "isobject": "3.0.1", + "static-extend": "0.1.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + } + } + }, + "clean-css": { + "version": "3.4.28", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.28.tgz", + "integrity": "sha1-vxlF6C/ICPVWlebd6uwBQA79A/8=", + "dev": true, + "requires": { + "commander": "2.8.1", + "source-map": "0.4.4" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": "1.0.1" + } + } + } + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + }, + "clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true + }, + "clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", + "dev": true + }, + "cloneable-readable": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.2.tgz", + "integrity": "sha512-Bq6+4t+lbM8vhTs/Bef5c5AdEMtapp/iFb6+s4/Hh9MVTt8OLKH7ZOOZSCT+Ys7hsHvqv0GuMPJ1lnQJVHvxpg==", + "dev": true, + "requires": { + "inherits": "2.0.3", + "process-nextick-args": "2.0.0", + "readable-stream": "2.3.6" + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "1.0.0", + "object-visit": "1.0.1" + } + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true + }, + "commander": { + "version": "2.8.1", + "resolved": "http://registry.npmjs.org/commander/-/commander-2.8.1.tgz", + "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", + "dev": true, + "requires": { + "graceful-readlink": "1.0.1" + } + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-with-sourcemaps": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", + "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", + "dev": true, + "requires": { + "source-map": "0.6.1" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "1.0.2" + } + }, + "dateformat": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", + "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "requires": { + "clone": "1.0.4" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "1.0.2", + "isobject": "3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + } + } + }, + "del": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", + "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", + "dev": true, + "requires": { + "globby": "6.1.0", + "is-path-cwd": "1.0.0", + "is-path-in-cwd": "1.0.1", + "p-map": "1.2.0", + "pify": "3.0.0", + "rimraf": "2.6.2" + } + }, + "deprecated": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/deprecated/-/deprecated-0.0.1.tgz", + "integrity": "sha1-+cmvVGSvoeepcUWKi97yqpTVuxk=", + "dev": true + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true + }, + "duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "dev": true, + "requires": { + "readable-stream": "1.1.14" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "editions": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/editions/-/editions-1.3.4.tgz", + "integrity": "sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg==", + "dev": true + }, + "end-of-stream": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-0.1.5.tgz", + "integrity": "sha1-jhdyBsPICDfYVjLouTWd/osvbq8=", + "dev": true, + "requires": { + "once": "1.3.3" + }, + "dependencies": { + "once": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", + "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + } + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "0.2.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "requires": { + "homedir-polyfill": "1.0.1" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "1.0.0", + "is-extendable": "1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + } + } + }, + "fancy-log": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.2.tgz", + "integrity": "sha1-9BEl49hPLn2JpD0G2VjI94vha+E=", + "dev": true, + "requires": { + "ansi-gray": "0.1.1", + "color-support": "1.1.3", + "time-stamp": "1.1.0" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "find-index": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz", + "integrity": "sha1-Z101iyyjiS15Whq0cjL4tuLg3eQ=", + "dev": true + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + }, + "findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "dev": true, + "requires": { + "detect-file": "1.0.0", + "is-glob": "3.1.0", + "micromatch": "3.1.10", + "resolve-dir": "1.0.1" + } + }, + "fined": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.1.0.tgz", + "integrity": "sha1-s33IRLdqL15wgeiE98CuNE8VNHY=", + "dev": true, + "requires": { + "expand-tilde": "2.0.2", + "is-plain-object": "2.0.4", + "object.defaults": "1.1.0", + "object.pick": "1.3.0", + "parse-filepath": "1.0.2" + } + }, + "first-chunk-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz", + "integrity": "sha1-Wb+1DNkF9g18OUzT2ayqtOatk04=", + "dev": true + }, + "flagged-respawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.0.tgz", + "integrity": "sha1-Tnmumy6zi/hrO7Vr8+ClaqX8q9c=", + "dev": true + }, + "font-awesome": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", + "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=" + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "requires": { + "for-in": "1.0.2" + } + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "0.2.2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "gaze": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz", + "integrity": "sha1-QLcJU30k0dRXZ9takIaJ3+aaxE8=", + "dev": true, + "requires": { + "globule": "0.1.0" + } + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "git-rev-sync": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/git-rev-sync/-/git-rev-sync-1.12.0.tgz", + "integrity": "sha1-RGhAbH5sO6TPRYeZnhrbKNnRr1U=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5", + "graceful-fs": "4.1.11", + "shelljs": "0.7.7" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "glob-stream": { + "version": "3.1.18", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-3.1.18.tgz", + "integrity": "sha1-kXCl8St5Awb9/lmPMT+PeVT9FDs=", + "dev": true, + "requires": { + "glob": "4.5.3", + "glob2base": "0.0.12", + "minimatch": "2.0.10", + "ordered-read-streams": "0.1.0", + "through2": "0.6.5", + "unique-stream": "1.0.0" + }, + "dependencies": { + "glob": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz", + "integrity": "sha1-xstz0yJsHv7wTePFbQEvAzd+4V8=", + "dev": true, + "requires": { + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "2.0.10", + "once": "1.4.0" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "minimatch": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", + "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "dev": true, + "requires": { + "readable-stream": "1.0.34", + "xtend": "4.0.1" + } + } + } + }, + "glob-watcher": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-0.0.6.tgz", + "integrity": "sha1-uVtKjfdLOcgymLDAXJeLTZo7cQs=", + "dev": true, + "requires": { + "gaze": "0.5.2" + } + }, + "glob2base": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/glob2base/-/glob2base-0.0.12.tgz", + "integrity": "sha1-nUGbPijxLoOjYhZKJ3BVkiycDVY=", + "dev": true, + "requires": { + "find-index": "0.1.1" + } + }, + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "requires": { + "global-prefix": "1.0.2", + "is-windows": "1.0.2", + "resolve-dir": "1.0.1" + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "requires": { + "expand-tilde": "2.0.2", + "homedir-polyfill": "1.0.1", + "ini": "1.3.5", + "is-windows": "1.0.2", + "which": "1.3.1" + } + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "1.0.2", + "glob": "7.1.3", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "globule": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/globule/-/globule-0.1.0.tgz", + "integrity": "sha1-2cjt3h2nnRJaFRt5UzuXhnY0auU=", + "dev": true, + "requires": { + "glob": "3.1.21", + "lodash": "1.0.2", + "minimatch": "0.2.14" + }, + "dependencies": { + "glob": { + "version": "3.1.21", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", + "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", + "dev": true, + "requires": { + "graceful-fs": "1.2.3", + "inherits": "1.0.2", + "minimatch": "0.2.14" + } + }, + "graceful-fs": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", + "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", + "dev": true + }, + "inherits": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", + "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", + "dev": true + }, + "minimatch": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "dev": true, + "requires": { + "lru-cache": "2.7.3", + "sigmund": "1.0.1" + } + } + } + }, + "glogg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.1.tgz", + "integrity": "sha512-ynYqXLoluBKf9XGR1gA59yEJisIL7YHEH4xr3ZziHB5/yl4qWfaK8Js9jGe6gBGCSCKVqiyO30WnRZADvemUNw==", + "dev": true, + "requires": { + "sparkles": "1.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true + }, + "gulp": { + "version": "3.9.1", + "resolved": "http://registry.npmjs.org/gulp/-/gulp-3.9.1.tgz", + "integrity": "sha1-VxzkWSjdQK9lFPxAEYZgFsE4RbQ=", + "dev": true, + "requires": { + "archy": "1.0.0", + "chalk": "1.1.3", + "deprecated": "0.0.1", + "gulp-util": "3.0.8", + "interpret": "1.1.0", + "liftoff": "2.5.0", + "minimist": "1.2.0", + "orchestrator": "0.3.8", + "pretty-hrtime": "1.0.3", + "semver": "4.3.6", + "tildify": "1.2.0", + "v8flags": "2.1.1", + "vinyl-fs": "0.3.14" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "gulp-concat": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.1.tgz", + "integrity": "sha1-Yz0WyV2IUEYorQJmVmPO5aR5M1M=", + "dev": true, + "requires": { + "concat-with-sourcemaps": "1.1.0", + "through2": "2.0.3", + "vinyl": "2.2.0" + }, + "dependencies": { + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + }, + "vinyl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", + "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", + "dev": true, + "requires": { + "clone": "2.1.2", + "clone-buffer": "1.0.0", + "clone-stats": "1.0.0", + "cloneable-readable": "1.1.2", + "remove-trailing-separator": "1.1.0", + "replace-ext": "1.0.0" + } + } + } + }, + "gulp-cssmin": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/gulp-cssmin/-/gulp-cssmin-0.2.0.tgz", + "integrity": "sha1-h6s8ad05sg1dljVcZQStakR7HnI=", + "dev": true, + "requires": { + "clean-css": "3.4.28", + "filesize": "2.0.4", + "graceful-fs": "4.1.11", + "gulp-rename": "1.1.0", + "gulp-util": "2.2.20", + "map-stream": "0.0.4", + "temp-write": "0.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "0.2.1", + "resolved": "http://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", + "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=", + "dev": true + }, + "ansi-styles": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", + "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=", + "dev": true + }, + "chalk": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", + "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", + "dev": true, + "requires": { + "ansi-styles": "1.1.0", + "escape-string-regexp": "1.0.5", + "has-ansi": "0.1.0", + "strip-ansi": "0.3.0", + "supports-color": "0.2.0" + } + }, + "dateformat": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", + "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", + "dev": true, + "requires": { + "get-stdin": "4.0.1", + "meow": "3.7.0" + } + }, + "filesize": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-2.0.4.tgz", + "integrity": "sha1-eAWUHGD83+Y/RtfqNYxZreEcEyU=", + "dev": true + }, + "gulp-rename": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-1.1.0.tgz", + "integrity": "sha1-kwkKqvTThsB/IFOKaIjxXvunJ6E=", + "dev": true, + "requires": { + "map-stream": "0.0.4" + } + }, + "gulp-util": { + "version": "2.2.20", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-2.2.20.tgz", + "integrity": "sha1-1xRuVyiRC9jwR6awseVJvCLb1kw=", + "dev": true, + "requires": { + "chalk": "0.5.1", + "dateformat": "1.0.12", + "lodash._reinterpolate": "2.4.1", + "lodash.template": "2.4.1", + "minimist": "0.2.0", + "multipipe": "0.1.2", + "through2": "0.5.1", + "vinyl": "0.2.3" + } + }, + "has-ansi": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", + "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", + "dev": true, + "requires": { + "ansi-regex": "0.2.1" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "lodash._reinterpolate": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-2.4.1.tgz", + "integrity": "sha1-TxInqlqHEfxjL1sHofRgequLMiI=", + "dev": true + }, + "lodash.escape": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-2.4.1.tgz", + "integrity": "sha1-LOEsXghNsKV92l5dHu659dF1o7Q=", + "dev": true, + "requires": { + "lodash._escapehtmlchar": "2.4.1", + "lodash._reunescapedhtml": "2.4.1", + "lodash.keys": "2.4.1" + } + }, + "lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "dev": true, + "requires": { + "lodash._isnative": "2.4.1", + "lodash._shimkeys": "2.4.1", + "lodash.isobject": "2.4.1" + } + }, + "lodash.template": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-2.4.1.tgz", + "integrity": "sha1-nmEQB+32KRKal0qzxIuBez4c8g0=", + "dev": true, + "requires": { + "lodash._escapestringchar": "2.4.1", + "lodash._reinterpolate": "2.4.1", + "lodash.defaults": "2.4.1", + "lodash.escape": "2.4.1", + "lodash.keys": "2.4.1", + "lodash.templatesettings": "2.4.1", + "lodash.values": "2.4.1" + } + }, + "lodash.templatesettings": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-2.4.1.tgz", + "integrity": "sha1-6nbHXRHrhtTb6JqDiTu4YZKaxpk=", + "dev": true, + "requires": { + "lodash._reinterpolate": "2.4.1", + "lodash.escape": "2.4.1" + } + }, + "minimist": { + "version": "0.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.2.0.tgz", + "integrity": "sha1-Tf/lJdriuGTGbC4jxicdev3s784=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "strip-ansi": { + "version": "0.3.0", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", + "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", + "dev": true, + "requires": { + "ansi-regex": "0.2.1" + } + }, + "supports-color": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", + "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=", + "dev": true + }, + "through2": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", + "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", + "dev": true, + "requires": { + "readable-stream": "1.0.34", + "xtend": "3.0.0" + } + }, + "vinyl": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.2.3.tgz", + "integrity": "sha1-vKk4IJWC7FpJrVOKAPofEl5RMlI=", + "dev": true, + "requires": { + "clone-stats": "0.0.1" + } + }, + "xtend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", + "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=", + "dev": true + } + } + }, + "gulp-rename": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-1.4.0.tgz", + "integrity": "sha512-swzbIGb/arEoFK89tPY58vg3Ok1bw+d35PfUNwWqdo7KM4jkmuGA78JiDNqR+JeZFaeeHnRg9N7aihX3YPmsyg==", + "dev": true + }, + "gulp-replace": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.0.0.tgz", + "integrity": "sha512-lgdmrFSI1SdhNMXZQbrC75MOl1UjYWlOWNbNRnz+F/KHmgxt3l6XstBoAYIdadwETFyG/6i+vWUSCawdC3pqOw==", + "dev": true, + "requires": { + "istextorbinary": "2.2.1", + "readable-stream": "2.3.6", + "replacestream": "4.0.3" + } + }, + "gulp-uglify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/gulp-uglify/-/gulp-uglify-3.0.1.tgz", + "integrity": "sha512-KVffbGY9d4Wv90bW/B1KZJyunLMyfHTBbilpDvmcrj5Go0/a1G3uVpt+1gRBWSw/11dqR3coJ1oWNTt1AiXuWQ==", + "dev": true, + "requires": { + "gulplog": "1.0.0", + "has-gulplog": "0.1.0", + "lodash": "4.17.11", + "make-error-cause": "1.2.2", + "safe-buffer": "5.1.2", + "through2": "2.0.3", + "uglify-js": "3.4.9", + "vinyl-sourcemaps-apply": "0.2.1" + }, + "dependencies": { + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "uglify-js": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", + "dev": true, + "requires": { + "commander": "2.17.1", + "source-map": "0.6.1" + } + } + } + }, + "gulp-util": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", + "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", + "dev": true, + "requires": { + "array-differ": "1.0.0", + "array-uniq": "1.0.3", + "beeper": "1.1.1", + "chalk": "1.1.3", + "dateformat": "2.2.0", + "fancy-log": "1.3.2", + "gulplog": "1.0.0", + "has-gulplog": "0.1.0", + "lodash._reescape": "3.0.0", + "lodash._reevaluate": "3.0.0", + "lodash._reinterpolate": "3.0.0", + "lodash.template": "3.6.2", + "minimist": "1.2.0", + "multipipe": "0.1.2", + "object-assign": "3.0.0", + "replace-ext": "0.0.1", + "through2": "2.0.3", + "vinyl": "0.5.3" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "dev": true, + "requires": { + "glogg": "1.0.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "has-gulplog": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", + "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", + "dev": true, + "requires": { + "sparkles": "1.0.1" + } + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "2.0.6", + "has-values": "1.0.0", + "isobject": "3.0.1" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "homedir-polyfill": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", + "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", + "dev": true, + "requires": { + "parse-passwd": "1.0.0" + } + }, + "hosted-git-info": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "dev": true + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "2.0.1" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "interpret": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", + "dev": true + }, + "is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "requires": { + "is-relative": "1.0.0", + "is-windows": "1.0.2" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true, + "requires": { + "builtin-modules": "1.1.1" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "1.0.1" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "1.0.2" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "3.0.1" + } + }, + "is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "requires": { + "is-unc-path": "1.0.0" + } + }, + "is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "requires": { + "unc-path-regex": "0.1.2" + } + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "istextorbinary": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-2.2.1.tgz", + "integrity": "sha512-TS+hoFl8Z5FAFMK38nhBkdLt44CclNRgDHWeMgsV8ko3nDlr/9UI2Sf839sW7enijf8oKsZYXRvM8g0it9Zmcw==", + "dev": true, + "requires": { + "binaryextensions": "2.1.1", + "editions": "1.3.4", + "textextensions": "2.2.0" + } + }, + "jquery": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz", + "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==" + }, + "jquery-validation": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/jquery-validation/-/jquery-validation-1.18.0.tgz", + "integrity": "sha512-+MK0pvoegfLrJnwtCU6tx305Vgp9HWevpmdVwDf5TthmINkn0wqqLD0bpa75EERlHsBBjMmza//ppx5ZQPnW3Q==", + "requires": { + "jquery": "3.3.1" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "liftoff": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-2.5.0.tgz", + "integrity": "sha1-IAkpG7Mc6oYbvxCnwVooyvdcMew=", + "dev": true, + "requires": { + "extend": "3.0.2", + "findup-sync": "2.0.0", + "fined": "1.1.0", + "flagged-respawn": "1.0.0", + "is-plain-object": "2.0.4", + "object.map": "1.0.1", + "rechoir": "0.6.2", + "resolve": "1.8.1" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "strip-bom": "2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "0.2.1" + } + } + } + }, + "lodash": { + "version": "1.0.2", + "resolved": "http://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", + "integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=", + "dev": true + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._basetostring": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", + "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=", + "dev": true + }, + "lodash._basevalues": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", + "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", + "dev": true + }, + "lodash._escapehtmlchar": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._escapehtmlchar/-/lodash._escapehtmlchar-2.4.1.tgz", + "integrity": "sha1-32fDu2t+jh6DGrSL+geVuSr+iZ0=", + "dev": true, + "requires": { + "lodash._htmlescapes": "2.4.1" + } + }, + "lodash._escapestringchar": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._escapestringchar/-/lodash._escapestringchar-2.4.1.tgz", + "integrity": "sha1-7P4iYYoq3lC/7qQ5N+Ud9m8O23I=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._htmlescapes": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._htmlescapes/-/lodash._htmlescapes-2.4.1.tgz", + "integrity": "sha1-MtFL8IRLbeb4tioFG09nwii2JMs=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash._isnative": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._isnative/-/lodash._isnative-2.4.1.tgz", + "integrity": "sha1-PqZAS3hKe+g2x7V1gOHN95sUgyw=", + "dev": true + }, + "lodash._objecttypes": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz", + "integrity": "sha1-fAt/admKH3ZSn4kLDNsbTf7BHBE=", + "dev": true + }, + "lodash._reescape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", + "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=", + "dev": true + }, + "lodash._reevaluate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", + "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=", + "dev": true + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, + "lodash._reunescapedhtml": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._reunescapedhtml/-/lodash._reunescapedhtml-2.4.1.tgz", + "integrity": "sha1-dHxPxAED6zu4oJduVx96JlnpO6c=", + "dev": true, + "requires": { + "lodash._htmlescapes": "2.4.1", + "lodash.keys": "2.4.1" + }, + "dependencies": { + "lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "dev": true, + "requires": { + "lodash._isnative": "2.4.1", + "lodash._shimkeys": "2.4.1", + "lodash.isobject": "2.4.1" + } + } + } + }, + "lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", + "dev": true + }, + "lodash._shimkeys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz", + "integrity": "sha1-bpzJZm/wgfC1psl4uD4kLmlJ0gM=", + "dev": true, + "requires": { + "lodash._objecttypes": "2.4.1" + } + }, + "lodash.defaults": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-2.4.1.tgz", + "integrity": "sha1-p+iIXwXmiFEUS24SqPNngCa8TFQ=", + "dev": true, + "requires": { + "lodash._objecttypes": "2.4.1", + "lodash.keys": "2.4.1" + }, + "dependencies": { + "lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "dev": true, + "requires": { + "lodash._isnative": "2.4.1", + "lodash._shimkeys": "2.4.1", + "lodash.isobject": "2.4.1" + } + } + } + }, + "lodash.escape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", + "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", + "dev": true, + "requires": { + "lodash._root": "3.0.1" + } + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.isobject": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", + "integrity": "sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=", + "dev": true, + "requires": { + "lodash._objecttypes": "2.4.1" + } + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "3.9.1", + "lodash.isarguments": "3.1.0", + "lodash.isarray": "3.0.4" + } + }, + "lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", + "dev": true + }, + "lodash.template": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", + "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", + "dev": true, + "requires": { + "lodash._basecopy": "3.0.1", + "lodash._basetostring": "3.0.1", + "lodash._basevalues": "3.0.0", + "lodash._isiterateecall": "3.0.9", + "lodash._reinterpolate": "3.0.0", + "lodash.escape": "3.2.0", + "lodash.keys": "3.1.2", + "lodash.restparam": "3.6.1", + "lodash.templatesettings": "3.1.1" + } + }, + "lodash.templatesettings": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", + "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", + "dev": true, + "requires": { + "lodash._reinterpolate": "3.0.0", + "lodash.escape": "3.2.0" + } + }, + "lodash.values": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-2.4.1.tgz", + "integrity": "sha1-q/UUQ2s8twUAFieXjLzzCxKA7qQ=", + "dev": true, + "requires": { + "lodash.keys": "2.4.1" + }, + "dependencies": { + "lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "dev": true, + "requires": { + "lodash._isnative": "2.4.1", + "lodash._shimkeys": "2.4.1", + "lodash.isobject": "2.4.1" + } + } + } + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "0.4.1", + "signal-exit": "3.0.2" + } + }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, + "make-error": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "dev": true + }, + "make-error-cause": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/make-error-cause/-/make-error-cause-1.2.2.tgz", + "integrity": "sha1-3wOI/NCzeBbf8KX7gQiTl3fcvJ0=", + "dev": true, + "requires": { + "make-error": "1.3.5" + } + }, + "make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "map-stream": { + "version": "0.0.4", + "resolved": "http://registry.npmjs.org/map-stream/-/map-stream-0.0.4.tgz", + "integrity": "sha1-XsbekCE+9sey65Nn6a3o2k79tos=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "1.0.1" + } + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "2.1.0", + "decamelize": "1.2.0", + "loud-rejection": "1.6.0", + "map-obj": "1.0.1", + "minimist": "1.2.0", + "normalize-package-data": "2.4.0", + "object-assign": "4.1.1", + "read-pkg-up": "1.0.1", + "redent": "1.0.0", + "trim-newlines": "1.0.0" + } + }, + "merge-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", + "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", + "dev": true, + "requires": { + "readable-stream": "2.3.6" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "dev": true, + "requires": { + "for-in": "1.0.2", + "is-extendable": "1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "multipipe": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", + "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", + "dev": true, + "requires": { + "duplexer2": "0.0.2" + } + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "fragment-cache": "0.2.1", + "is-windows": "1.0.2", + "kind-of": "6.0.2", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } + }, + "natives": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.6.tgz", + "integrity": "sha512-6+TDFewD4yxY14ptjKaS63GVdtKiES1pTPyxn9Jb0rBqPMZ7VcCiooEhPNsr+mqHtMGxa/5c/HhcC4uPEUw/nA==", + "dev": true + }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "dev": true, + "requires": { + "hosted-git-info": "2.7.1", + "is-builtin-module": "1.0.0", + "semver": "4.3.6", + "validate-npm-package-license": "3.0.4" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "0.1.1", + "define-property": "0.2.5", + "kind-of": "3.2.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "3.0.1" + } + }, + "object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "dev": true, + "requires": { + "array-each": "1.0.1", + "array-slice": "1.1.0", + "for-own": "1.0.0", + "isobject": "3.0.1" + } + }, + "object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", + "dev": true, + "requires": { + "for-own": "1.0.0", + "make-iterator": "1.0.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "3.0.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "orchestrator": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/orchestrator/-/orchestrator-0.3.8.tgz", + "integrity": "sha1-FOfp4nZPcxX7rBhOUGx6pt+UrX4=", + "dev": true, + "requires": { + "end-of-stream": "0.1.5", + "sequencify": "0.0.7", + "stream-consume": "0.1.1" + } + }, + "ordered-read-streams": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz", + "integrity": "sha1-/VZamvjrRHO6abbtijQ1LLVS8SY=", + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "p-map": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", + "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", + "dev": true + }, + "parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", + "dev": true, + "requires": { + "is-absolute": "1.0.0", + "map-cache": "0.2.2", + "path-root": "0.1.1" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "1.3.2" + } + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", + "dev": true, + "requires": { + "path-root-regex": "0.1.2" + } + }, + "path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", + "dev": true + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "1.4.1", + "once": "1.4.0" + }, + "dependencies": { + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "1.4.0" + } + } + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "1.1.0", + "normalize-package-data": "2.4.0", + "path-type": "1.1.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "1.1.2", + "read-pkg": "1.1.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "1.8.1" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "2.1.0", + "strip-indent": "1.0.1" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "3.0.2", + "safe-regex": "1.1.0" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "1.0.2" + } + }, + "replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", + "dev": true + }, + "replacestream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/replacestream/-/replacestream-4.0.3.tgz", + "integrity": "sha512-AC0FiLS352pBBiZhd4VXB1Ab/lh0lEgpP+GGvZqbQh8a5cmXVoTe5EX/YeTFArnp4SRGTHh1qCHu9lGs1qG8sA==", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5", + "object-assign": "4.1.1", + "readable-stream": "2.3.6" + } + }, + "resolve": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "dev": true, + "requires": { + "path-parse": "1.0.6" + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "requires": { + "expand-tilde": "2.0.2", + "global-modules": "1.0.0" + } + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "0.1.15" + } + }, + "semver": { + "version": "4.3.6", + "resolved": "http://registry.npmjs.org/semver/-/semver-4.3.6.tgz", + "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", + "dev": true + }, + "sequencify": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/sequencify/-/sequencify-0.0.7.tgz", + "integrity": "sha1-kM/xnQLgcCf9dn9erT57ldHnOAw=", + "dev": true + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "split-string": "3.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "shelljs": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.7.tgz", + "integrity": "sha1-svXHfvlxSPS09uImguELuoZnz/E=", + "dev": true, + "requires": { + "glob": "7.1.3", + "interpret": "1.1.0", + "rechoir": "0.6.2" + } + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "0.11.2", + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "map-cache": "0.2.2", + "source-map": "0.5.7", + "source-map-resolve": "0.5.2", + "use": "3.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "1.0.0", + "isobject": "3.0.1", + "snapdragon-util": "3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "2.1.2", + "decode-uri-component": "0.2.0", + "resolve-url": "0.2.1", + "source-map-url": "0.4.0", + "urix": "0.1.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "sparkles": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", + "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "dev": true + }, + "spdx-correct": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.2.tgz", + "integrity": "sha512-q9hedtzyXHr5S0A1vEPoK/7l8NpfkFYTq6iCY+Pno2ZbdZR6WexZFtqeVGkGxW3TEJMN914Z55EnAGMmenlIQQ==", + "dev": true, + "requires": { + "spdx-expression-parse": "3.0.0", + "spdx-license-ids": "3.0.1" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "2.2.0", + "spdx-license-ids": "3.0.1" + } + }, + "spdx-license-ids": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.1.tgz", + "integrity": "sha512-TfOfPcYGBB5sDuPn3deByxPhmfegAhpDYKSOXZQN81Oyrrif8ZCodOLzK3AesELnCx03kikhyDwh0pfvvQvF8w==", + "dev": true + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "3.0.2" + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "0.2.5", + "object-copy": "0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + } + } + }, + "stream-consume": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.1.tgz", + "integrity": "sha512-tNa3hzgkjEP7XbCkbRXe1jpg+ievoa0O4SCFlMOYEscGSS4JJsckGL8swUyAa/ApGU3Ae4t6Honor4HhL+tRyg==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-bom": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-1.0.0.tgz", + "integrity": "sha1-hbiGLzhEtabV7IRnqTWYFzo295Q=", + "dev": true, + "requires": { + "first-chunk-stream": "1.0.0", + "is-utf8": "0.2.1" + } + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "4.0.1" + } + }, + "temp-write": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/temp-write/-/temp-write-0.1.1.tgz", + "integrity": "sha1-C2Rng43Xf79/YqDJPah5cy/9qTI=", + "dev": true, + "requires": { + "graceful-fs": "2.0.3", + "tempfile": "0.1.3" + }, + "dependencies": { + "graceful-fs": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", + "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=", + "dev": true + } + } + }, + "tempfile": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-0.1.3.tgz", + "integrity": "sha1-fWtxAEcznTn4RzJ6BW2t8YMQMBA=", + "dev": true, + "requires": { + "uuid": "1.4.2" + } + }, + "textextensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-2.2.0.tgz", + "integrity": "sha512-j5EMxnryTvKxwH2Cq+Pb43tsf6sdEgw6Pdwxk83mPaq0ToeFJt6WE4J3s5BqY7vmjlLgkgXvhtXUxo80FyBhCA==", + "dev": true + }, + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true, + "requires": { + "readable-stream": "2.3.6", + "xtend": "4.0.1" + } + }, + "tildify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tildify/-/tildify-1.2.0.tgz", + "integrity": "sha1-3OwD9V3Km3qj5bBPIYF+tW5jWIo=", + "dev": true, + "requires": { + "os-homedir": "1.0.2" + } + }, + "time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "regex-not": "1.0.2", + "safe-regex": "1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "3.0.0", + "repeat-string": "1.6.1" + } + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "uglify-es": { + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", + "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", + "dev": true, + "requires": { + "commander": "2.13.0", + "source-map": "0.6.1" + }, + "dependencies": { + "commander": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", + "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", + "dev": true + } + } + }, + "unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", + "dev": true + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, + "requires": { + "arr-union": "3.1.0", + "get-value": "2.0.6", + "is-extendable": "0.1.1", + "set-value": "0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "to-object-path": "0.3.0" + } + } + } + }, + "unique-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-1.0.0.tgz", + "integrity": "sha1-1ZpKdUJ0R9mqbJHnAmP40mpLEEs=", + "dev": true + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "0.3.1", + "isobject": "3.0.1" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "2.0.6", + "has-values": "0.1.4", + "isobject": "2.1.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "user-home": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", + "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "uuid": { + "version": "1.4.2", + "resolved": "http://registry.npmjs.org/uuid/-/uuid-1.4.2.tgz", + "integrity": "sha1-RTAZ9oaWam34PNxSROfJkOzDMvw=", + "dev": true + }, + "v8flags": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz", + "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", + "dev": true, + "requires": { + "user-home": "1.1.1" + } + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "3.0.2", + "spdx-expression-parse": "3.0.0" + } + }, + "vinyl": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", + "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", + "dev": true, + "requires": { + "clone": "1.0.4", + "clone-stats": "0.0.1", + "replace-ext": "0.0.1" + } + }, + "vinyl-fs": { + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-0.3.14.tgz", + "integrity": "sha1-mmhRzhysHBzqX+hsCTHWIMLPqeY=", + "dev": true, + "requires": { + "defaults": "1.0.3", + "glob-stream": "3.1.18", + "glob-watcher": "0.0.6", + "graceful-fs": "3.0.11", + "mkdirp": "0.5.1", + "strip-bom": "1.0.0", + "through2": "0.6.5", + "vinyl": "0.4.6" + }, + "dependencies": { + "clone": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", + "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", + "dev": true + }, + "graceful-fs": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", + "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", + "dev": true, + "requires": { + "natives": "1.1.6" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "dev": true, + "requires": { + "readable-stream": "1.0.34", + "xtend": "4.0.1" + } + }, + "vinyl": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", + "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", + "dev": true, + "requires": { + "clone": "0.2.0", + "clone-stats": "0.0.1" + } + } + } + }, + "vinyl-sourcemaps-apply": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", + "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", + "dev": true, + "requires": { + "source-map": "0.5.7" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "2.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + } + } +} diff --git a/IdentityServer/package.json b/IdentityServer/package.json new file mode 100644 index 0000000..cd2788a --- /dev/null +++ b/IdentityServer/package.json @@ -0,0 +1,48 @@ +{ + "author": "Uncled1023", + "dependencies": { + "awesome-bootstrap-checkbox": "^1.0.1", + "bootstrap": "3.3.7", + "font-awesome": "^4.7.0", + "jquery": "^3.3.1", + "jquery-validation": "^1.17.0" + }, + "description": "Teknik Services", + "devDependencies": { + "del": "^3.0.0", + "git-rev-sync": "^1.12.0", + "gulp": "3.9.1", + "gulp-concat": "^2.6.1", + "gulp-cssmin": "^0.2.0", + "gulp-rename": "^1.3.0", + "gulp-replace": "^1.0.0", + "gulp-uglify": "^3.0.0", + "merge-stream": "^1.0.1", + "pump": "^3.0.0", + "rimraf": "^2.6.2", + "uglify-es": "^3.3.9" + }, + "keywords": [ + "Teknik", + "Identity", + "Authentication" + ], + "license": "BSD-3-Clause", + "main": "webpack.config.js", + "name": "teknik", + "repository": { + "type": "git", + "url": "https://git.teknik.io/Teknikode/Teknik" + }, + "scripts": { + "pre-publish": "npm install && gulp clean && gulp copy-assets && gulp watch", + "build": "npm install && gulp clean && gulp copy-assets && gulp min && gulp update-version", + "gulp": "gulp" + }, + "version": "2.1.0", + "-vs-binding": { + "ProjectOpened": [ + "pre-publish" + ] + } +} diff --git a/Logging/Logging.csproj b/Logging/Logging.csproj index 8b2825e..ff1aa6b 100644 --- a/Logging/Logging.csproj +++ b/Logging/Logging.csproj @@ -5,6 +5,7 @@ Teknik.Logging Teknik.Logging win-x86;win-x64;linux-x64;linux-arm;osx-x64 + Debug;Release;Test diff --git a/MailService/MailService.csproj b/MailService/MailService.csproj index d81dfda..1ec4361 100644 --- a/MailService/MailService.csproj +++ b/MailService/MailService.csproj @@ -8,7 +8,7 @@ - + diff --git a/Teknik.sln b/Teknik.sln index ed0a41e..64e0dca 100644 --- a/Teknik.sln +++ b/Teknik.sln @@ -26,40 +26,63 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MailService", "MailService\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GitService", "GitService\GitService.csproj", "{014879B1-DDD5-4F8C-9597-6D7960912CF0}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IdentityServer", "IdentityServer\IdentityServer.csproj", "{5A7625F7-A162-430D-8725-318E2CFA8BDD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU + Test|Any CPU = Test|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {1E52F0D0-9E89-4022-A905-C685EF3564E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1E52F0D0-9E89-4022-A905-C685EF3564E1}.Debug|Any CPU.Build.0 = Debug|Any CPU {1E52F0D0-9E89-4022-A905-C685EF3564E1}.Release|Any CPU.ActiveCfg = Release|Any CPU {1E52F0D0-9E89-4022-A905-C685EF3564E1}.Release|Any CPU.Build.0 = Release|Any CPU + {1E52F0D0-9E89-4022-A905-C685EF3564E1}.Test|Any CPU.ActiveCfg = Release|Any CPU + {1E52F0D0-9E89-4022-A905-C685EF3564E1}.Test|Any CPU.Build.0 = Release|Any CPU {7A1F40CA-7C37-4B7B-973A-CDCDD424F31F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7A1F40CA-7C37-4B7B-973A-CDCDD424F31F}.Debug|Any CPU.Build.0 = Debug|Any CPU {7A1F40CA-7C37-4B7B-973A-CDCDD424F31F}.Release|Any CPU.ActiveCfg = Release|Any CPU {7A1F40CA-7C37-4B7B-973A-CDCDD424F31F}.Release|Any CPU.Build.0 = Release|Any CPU + {7A1F40CA-7C37-4B7B-973A-CDCDD424F31F}.Test|Any CPU.ActiveCfg = Test|Any CPU + {7A1F40CA-7C37-4B7B-973A-CDCDD424F31F}.Test|Any CPU.Build.0 = Test|Any CPU {DD521101-7F10-407A-9788-49283D946FDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DD521101-7F10-407A-9788-49283D946FDA}.Debug|Any CPU.Build.0 = Debug|Any CPU {DD521101-7F10-407A-9788-49283D946FDA}.Release|Any CPU.ActiveCfg = Release|Any CPU {DD521101-7F10-407A-9788-49283D946FDA}.Release|Any CPU.Build.0 = Release|Any CPU + {DD521101-7F10-407A-9788-49283D946FDA}.Test|Any CPU.ActiveCfg = Test|Any CPU + {DD521101-7F10-407A-9788-49283D946FDA}.Test|Any CPU.Build.0 = Test|Any CPU {3CAB11F5-9B07-4D17-BB00-725149087AB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3CAB11F5-9B07-4D17-BB00-725149087AB0}.Debug|Any CPU.Build.0 = Debug|Any CPU {3CAB11F5-9B07-4D17-BB00-725149087AB0}.Release|Any CPU.ActiveCfg = Release|Any CPU {3CAB11F5-9B07-4D17-BB00-725149087AB0}.Release|Any CPU.Build.0 = Release|Any CPU + {3CAB11F5-9B07-4D17-BB00-725149087AB0}.Test|Any CPU.ActiveCfg = Test|Any CPU + {3CAB11F5-9B07-4D17-BB00-725149087AB0}.Test|Any CPU.Build.0 = Test|Any CPU {F8823907-092C-4055-9F8E-D6756793C24A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F8823907-092C-4055-9F8E-D6756793C24A}.Debug|Any CPU.Build.0 = Debug|Any CPU {F8823907-092C-4055-9F8E-D6756793C24A}.Release|Any CPU.ActiveCfg = Release|Any CPU {F8823907-092C-4055-9F8E-D6756793C24A}.Release|Any CPU.Build.0 = Release|Any CPU + {F8823907-092C-4055-9F8E-D6756793C24A}.Test|Any CPU.ActiveCfg = Release|Any CPU + {F8823907-092C-4055-9F8E-D6756793C24A}.Test|Any CPU.Build.0 = Release|Any CPU {03636C30-DA61-4307-8934-2FCC3BAC3255}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {03636C30-DA61-4307-8934-2FCC3BAC3255}.Debug|Any CPU.Build.0 = Debug|Any CPU {03636C30-DA61-4307-8934-2FCC3BAC3255}.Release|Any CPU.ActiveCfg = Release|Any CPU {03636C30-DA61-4307-8934-2FCC3BAC3255}.Release|Any CPU.Build.0 = Release|Any CPU + {03636C30-DA61-4307-8934-2FCC3BAC3255}.Test|Any CPU.ActiveCfg = Release|Any CPU + {03636C30-DA61-4307-8934-2FCC3BAC3255}.Test|Any CPU.Build.0 = Release|Any CPU {014879B1-DDD5-4F8C-9597-6D7960912CF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {014879B1-DDD5-4F8C-9597-6D7960912CF0}.Debug|Any CPU.Build.0 = Debug|Any CPU {014879B1-DDD5-4F8C-9597-6D7960912CF0}.Release|Any CPU.ActiveCfg = Release|Any CPU {014879B1-DDD5-4F8C-9597-6D7960912CF0}.Release|Any CPU.Build.0 = Release|Any CPU + {014879B1-DDD5-4F8C-9597-6D7960912CF0}.Test|Any CPU.ActiveCfg = Release|Any CPU + {014879B1-DDD5-4F8C-9597-6D7960912CF0}.Test|Any CPU.Build.0 = Release|Any CPU + {5A7625F7-A162-430D-8725-318E2CFA8BDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5A7625F7-A162-430D-8725-318E2CFA8BDD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5A7625F7-A162-430D-8725-318E2CFA8BDD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5A7625F7-A162-430D-8725-318E2CFA8BDD}.Release|Any CPU.Build.0 = Release|Any CPU + {5A7625F7-A162-430D-8725-318E2CFA8BDD}.Test|Any CPU.ActiveCfg = Test|Any CPU + {5A7625F7-A162-430D-8725-318E2CFA8BDD}.Test|Any CPU.Build.0 = Test|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Teknik/Areas/API/Controllers/APIController.cs b/Teknik/Areas/API/APIController.cs similarity index 97% rename from Teknik/Areas/API/Controllers/APIController.cs rename to Teknik/Areas/API/APIController.cs index d666f9e..0cf6e13 100644 --- a/Teknik/Areas/API/Controllers/APIController.cs +++ b/Teknik/Areas/API/APIController.cs @@ -17,7 +17,6 @@ using Teknik.Logging; namespace Teknik.Areas.API.Controllers { - [TeknikAuthorize] [Area("API")] public class APIController : DefaultController { diff --git a/Teknik/Areas/API/V1/Controllers/APIv1Controller.cs b/Teknik/Areas/API/V1/Controllers/APIv1Controller.cs new file mode 100644 index 0000000..31f5017 --- /dev/null +++ b/Teknik/Areas/API/V1/Controllers/APIv1Controller.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Web; +using Teknik.Areas.Upload; +using Teknik.Areas.Paste; +using Teknik.Controllers; +using Teknik.Utilities; +using Teknik.Models; +using System.Text; +using MimeDetective; +using MimeDetective.Extensions; +using Teknik.Areas.Shortener.Models; +using nClam; +using Teknik.Filters; +using Teknik.Areas.API.V1.Models; +using Teknik.Areas.Users.Models; +using Teknik.Areas.Users.Utility; +using Teknik.Attributes; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Teknik.Configuration; +using Teknik.Data; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Threading.Tasks; +using Teknik.Areas.Shortener; +using Teknik.Logging; +using Teknik.Areas.API.Controllers; + +namespace Teknik.Areas.API.V1.Controllers +{ + public class APIv1Controller : APIController + { + public APIv1Controller(ILogger logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { } + } +} diff --git a/Teknik/Areas/API/V1/Controllers/AccountAPIv1Controller.cs b/Teknik/Areas/API/V1/Controllers/AccountAPIv1Controller.cs new file mode 100644 index 0000000..ce66bd8 --- /dev/null +++ b/Teknik/Areas/API/V1/Controllers/AccountAPIv1Controller.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Teknik.Configuration; +using Teknik.Data; +using Teknik.Logging; + +namespace Teknik.Areas.API.V1.Controllers +{ + [Authorize(AuthenticationSchemes = "Bearer", Policy = "AnyAPI")] + public class AccountAPIv1Controller : APIv1Controller + { + public AccountAPIv1Controller(ILogger logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { } + + [HttpGet] + public IActionResult GetClaims() + { + return new JsonResult(from c in User.Claims select new { c.Type, c.Value }); + } + } +} \ No newline at end of file diff --git a/Teknik/Areas/API/V1/Controllers/AdminAPIv1Controller.cs b/Teknik/Areas/API/V1/Controllers/AdminAPIv1Controller.cs new file mode 100644 index 0000000..008f0fe --- /dev/null +++ b/Teknik/Areas/API/V1/Controllers/AdminAPIv1Controller.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Teknik.Areas.API.Controllers; +using Teknik.Configuration; +using Teknik.Controllers; +using Teknik.Data; +using Teknik.Logging; + +namespace Teknik.Areas.API.V1.Controllers +{ + [Authorize(AuthenticationSchemes = "Bearer", Roles = "Admin", Policy = "AnyAPI")] + public class AdminAPIv1Controller : APIv1Controller + { + public AdminAPIv1Controller(ILogger logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { } + } +} \ No newline at end of file diff --git a/Teknik/Areas/API/V1/Controllers/PasteAPIv1Controller.cs b/Teknik/Areas/API/V1/Controllers/PasteAPIv1Controller.cs new file mode 100644 index 0000000..1cd3848 --- /dev/null +++ b/Teknik/Areas/API/V1/Controllers/PasteAPIv1Controller.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Teknik.Areas.API.Controllers; +using Teknik.Areas.API.V1.Models; +using Teknik.Areas.Paste; +using Teknik.Areas.Users.Models; +using Teknik.Areas.Users.Utility; +using Teknik.Configuration; +using Teknik.Data; +using Teknik.Logging; +using Teknik.Utilities; + +namespace Teknik.Areas.API.V1.Controllers +{ + [Authorize(AuthenticationSchemes = "Bearer", Policy = "AnyAPI")] + public class PasteAPIv1Controller : APIv1Controller + { + public PasteAPIv1Controller(ILogger logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { } + + [HttpPost] + [AllowAnonymous] + public IActionResult Paste(PasteAPIv1Model model) + { + try + { + if (model != null && model.code != null) + { + Paste.Models.Paste paste = PasteHelper.CreatePaste(_config, _dbContext, model.code, model.title, model.syntax, model.expireUnit, model.expireLength, model.password, model.hide); + + // Associate this with the user if they are logged in + if (User.Identity.IsAuthenticated) + { + User foundUser = UserHelper.GetUser(_dbContext, User.Identity.Name); + if (foundUser != null) + { + paste.UserId = foundUser.UserId; + } + } + + _dbContext.Pastes.Add(paste); + _dbContext.SaveChanges(); + + return Json(new + { + result = new + { + id = paste.Url, + url = Url.SubRouteUrl("p", "Paste.View", new { type = "Full", url = paste.Url, password = model.password }), + title = paste.Title, + syntax = paste.Syntax, + expiration = paste.ExpireDate, + password = model.password + } + }); + } + return Json(new { error = new { message = "Invalid Paste Request" } }); + } + catch (Exception ex) + { + return Json(new { error = new { message = "Exception: " + ex.Message } }); + } + } + } +} \ No newline at end of file diff --git a/Teknik/Areas/API/V1/Controllers/ShortenAPIv1Controller.cs b/Teknik/Areas/API/V1/Controllers/ShortenAPIv1Controller.cs new file mode 100644 index 0000000..a0dc1b5 --- /dev/null +++ b/Teknik/Areas/API/V1/Controllers/ShortenAPIv1Controller.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Teknik.Areas.API.Controllers; +using Teknik.Areas.API.V1.Models; +using Teknik.Areas.Shortener; +using Teknik.Areas.Shortener.Models; +using Teknik.Areas.Users.Models; +using Teknik.Areas.Users.Utility; +using Teknik.Configuration; +using Teknik.Data; +using Teknik.Logging; +using Teknik.Utilities; + +namespace Teknik.Areas.API.V1.Controllers +{ + [Authorize(AuthenticationSchemes = "Bearer", Policy = "AnyAPI")] + public class ShortenAPIv1Controller : APIv1Controller + { + public ShortenAPIv1Controller(ILogger logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { } + + [HttpPost] + [AllowAnonymous] + public IActionResult Shorten(ShortenAPIv1Model model) + { + try + { + if (model.url.IsValidUrl()) + { + ShortenedUrl newUrl = ShortenerHelper.ShortenUrl(_dbContext, model.url, _config.ShortenerConfig.UrlLength); + + // Associate this with the user if they are logged in + if (User.Identity.IsAuthenticated) + { + User foundUser = UserHelper.GetUser(_dbContext, User.Identity.Name); + if (foundUser != null) + { + newUrl.UserId = foundUser.UserId; + } + } + + _dbContext.ShortenedUrls.Add(newUrl); + _dbContext.SaveChanges(); + + string shortUrl = string.Format("{0}://{1}/{2}", HttpContext.Request.Scheme, _config.ShortenerConfig.ShortenerHost, newUrl.ShortUrl); + if (_config.DevEnvironment) + { + shortUrl = Url.SubRouteUrl("shortened", "Shortener.View", new { url = newUrl.ShortUrl }); + } + + return Json(new + { + result = new + { + shortUrl = shortUrl, + originalUrl = model.url + } + }); + } + return Json(new { error = new { message = "Must be a valid Url" } }); + } + catch (Exception ex) + { + return Json(new { error = new { message = "Exception: " + ex.Message } }); + } + } + } +} \ No newline at end of file diff --git a/Teknik/Areas/API/Controllers/APIv1Controller.cs b/Teknik/Areas/API/V1/Controllers/UploadAPIv1Controller.cs similarity index 64% rename from Teknik/Areas/API/Controllers/APIv1Controller.cs rename to Teknik/Areas/API/V1/Controllers/UploadAPIv1Controller.cs index c08157c..1823fb3 100644 --- a/Teknik/Areas/API/Controllers/APIv1Controller.cs +++ b/Teknik/Areas/API/V1/Controllers/UploadAPIv1Controller.cs @@ -1,57 +1,35 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Web; -using Teknik.Areas.Upload; -using Teknik.Areas.Paste; -using Teknik.Controllers; -using Teknik.Utilities; -using Teknik.Models; -using System.Text; -using MimeDetective; -using MimeDetective.Extensions; -using Teknik.Areas.Shortener.Models; -using nClam; -using Teknik.Filters; -using Teknik.Areas.API.Models; -using Teknik.Areas.Users.Models; -using Teknik.Areas.Users.Utility; -using Teknik.Attributes; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; -using Microsoft.Extensions.Logging; -using Teknik.Configuration; -using Teknik.Data; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -using System.Threading.Tasks; -using Teknik.Areas.Shortener; +using Microsoft.Extensions.Logging; +using MimeDetective; +using MimeDetective.Extensions; +using nClam; +using Teknik.Areas.API.Controllers; +using Teknik.Areas.API.V1.Models; +using Teknik.Areas.Upload; +using Teknik.Areas.Users.Models; +using Teknik.Areas.Users.Utility; +using Teknik.Configuration; +using Teknik.Data; using Teknik.Logging; +using Teknik.Utilities; -namespace Teknik.Areas.API.Controllers +namespace Teknik.Areas.API.V1.Controllers { - [Authorize] - [TeknikAuthorize(AuthType.Basic)] - [Area("APIv1")] - public class APIv1Controller : DefaultController + [Authorize(AuthenticationSchemes = "Bearer", Policy = "AnyAPI")] + public class UploadAPIv1Controller : APIv1Controller { - public APIv1Controller(ILogger logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { } - - [AllowAnonymous] - public IActionResult Index() - { - return Redirect(Url.SubRouteUrl("help", "Help.API")); - } - - [HttpGet] - public IActionResult Get() - { - return new JsonResult(from c in User.Claims select new { c.Type, c.Value }); - } + public UploadAPIv1Controller(ILogger logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { } [HttpPost] [AllowAnonymous] - public async Task UploadAsync(APIv1UploadModel model) + public async Task Upload(UploadAPIv1Model model) { try { @@ -64,10 +42,10 @@ namespace Teknik.Areas.API.Controllers { maxUploadSize = _config.UploadConfig.MaxUploadSizeBasic; User user = UserHelper.GetUser(_dbContext, User.Identity.Name); - if (user.AccountType == AccountType.Premium) - { - maxUploadSize = _config.UploadConfig.MaxUploadSizePremium; - } + //if (user.AccountType == AccountType.Premium) + //{ + // maxUploadSize = _config.UploadConfig.MaxUploadSizePremium; + //} } if (model.file.Length <= maxUploadSize) { @@ -195,100 +173,10 @@ namespace Teknik.Areas.API.Controllers } return Json(new { error = new { message = "Uploads are Disabled" } }); } - catch(Exception ex) - { - return Json(new { error = new { message = "Exception: " + ex.Message } }); - } - } - - [HttpPost] - [AllowAnonymous] - public IActionResult Paste(APIv1PasteModel model) - { - try - { - if (model != null && model.code != null) - { - Paste.Models.Paste paste = PasteHelper.CreatePaste(_config, _dbContext, model.code, model.title, model.syntax, model.expireUnit, model.expireLength, model.password, model.hide); - - // Associate this with the user if they are logged in - if (User.Identity.IsAuthenticated) - { - User foundUser = UserHelper.GetUser(_dbContext, User.Identity.Name); - if (foundUser != null) - { - paste.UserId = foundUser.UserId; - } - } - - _dbContext.Pastes.Add(paste); - _dbContext.SaveChanges(); - - return Json(new - { - result = new - { - id = paste.Url, - url = Url.SubRouteUrl("p", "Paste.View", new { type = "Full", url = paste.Url, password = model.password }), - title = paste.Title, - syntax = paste.Syntax, - expiration = paste.ExpireDate, - password = model.password - } - }); - } - return Json(new { error = new { message = "Invalid Paste Request" } }); - } - catch (Exception ex) - { - return Json(new { error = new { message = "Exception: " + ex.Message } }); - } - } - - [HttpPost] - [AllowAnonymous] - public IActionResult Shorten(APIv1ShortenModel model) - { - try - { - if (model.url.IsValidUrl()) - { - ShortenedUrl newUrl = ShortenerHelper.ShortenUrl(_dbContext, model.url, _config.ShortenerConfig.UrlLength); - - // Associate this with the user if they are logged in - if (User.Identity.IsAuthenticated) - { - User foundUser = UserHelper.GetUser(_dbContext, User.Identity.Name); - if (foundUser != null) - { - newUrl.UserId = foundUser.UserId; - } - } - - _dbContext.ShortenedUrls.Add(newUrl); - _dbContext.SaveChanges(); - - string shortUrl = string.Format("{0}://{1}/{2}", HttpContext.Request.Scheme, _config.ShortenerConfig.ShortenerHost, newUrl.ShortUrl); - if (_config.DevEnvironment) - { - shortUrl = Url.SubRouteUrl("shortened", "Shortener.View", new { url = newUrl.ShortUrl }); - } - - return Json(new - { - result = new - { - shortUrl = shortUrl, - originalUrl = model.url - } - }); - } - return Json(new { error = new { message = "Must be a valid Url" } }); - } catch (Exception ex) { return Json(new { error = new { message = "Exception: " + ex.Message } }); } } } -} +} \ No newline at end of file diff --git a/Teknik/Areas/API/Models/APIv1BaseModel.cs b/Teknik/Areas/API/V1/Models/BaseAPIv1Model.cs similarity index 66% rename from Teknik/Areas/API/Models/APIv1BaseModel.cs rename to Teknik/Areas/API/V1/Models/BaseAPIv1Model.cs index ab231ec..3b615b2 100644 --- a/Teknik/Areas/API/Models/APIv1BaseModel.cs +++ b/Teknik/Areas/API/V1/Models/BaseAPIv1Model.cs @@ -3,13 +3,13 @@ using System.Collections.Generic; using System.Linq; using System.Web; -namespace Teknik.Areas.API.Models +namespace Teknik.Areas.API.V1.Models { - public class APIv1BaseModel + public class BaseAPIv1Model { public bool doNotTrack { get; set; } - public APIv1BaseModel() + public BaseAPIv1Model() { doNotTrack = false; } diff --git a/Teknik/Areas/API/V1/Models/GetAccessTokenAPIv1Model.cs b/Teknik/Areas/API/V1/Models/GetAccessTokenAPIv1Model.cs new file mode 100644 index 0000000..ec9a3a9 --- /dev/null +++ b/Teknik/Areas/API/V1/Models/GetAccessTokenAPIv1Model.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Teknik.Areas.API.V1.Models +{ + public class GetAccessTokenAPIv1Model : BaseAPIv1Model + { + public string ClientId { get; set; } + public string Secret { get; set; } + } +} diff --git a/Teknik/Areas/API/Models/APIv1PasteModel.cs b/Teknik/Areas/API/V1/Models/PasteAPIv1Model.cs similarity index 81% rename from Teknik/Areas/API/Models/APIv1PasteModel.cs rename to Teknik/Areas/API/V1/Models/PasteAPIv1Model.cs index 2914e3b..f0add52 100644 --- a/Teknik/Areas/API/Models/APIv1PasteModel.cs +++ b/Teknik/Areas/API/V1/Models/PasteAPIv1Model.cs @@ -1,6 +1,6 @@ -namespace Teknik.Areas.API.Models +namespace Teknik.Areas.API.V1.Models { - public class APIv1PasteModel : APIv1BaseModel + public class PasteAPIv1Model : BaseAPIv1Model { public string code { get; set; } @@ -16,7 +16,7 @@ public bool hide { get; set; } - public APIv1PasteModel() + public PasteAPIv1Model() { code = null; title = string.Empty; diff --git a/Teknik/Areas/API/Models/APIv1ShortenModel.cs b/Teknik/Areas/API/V1/Models/ShortenAPIv1Model.cs similarity index 61% rename from Teknik/Areas/API/Models/APIv1ShortenModel.cs rename to Teknik/Areas/API/V1/Models/ShortenAPIv1Model.cs index 6eaf7ab..8ded92b 100644 --- a/Teknik/Areas/API/Models/APIv1ShortenModel.cs +++ b/Teknik/Areas/API/V1/Models/ShortenAPIv1Model.cs @@ -3,13 +3,13 @@ using System.Collections.Generic; using System.Linq; using System.Web; -namespace Teknik.Areas.API.Models +namespace Teknik.Areas.API.V1.Models { - public class APIv1ShortenModel : APIv1BaseModel + public class ShortenAPIv1Model : BaseAPIv1Model { public string url { get; set; } - public APIv1ShortenModel() + public ShortenAPIv1Model() { url = string.Empty; } diff --git a/Teknik/Areas/API/Models/APIv1UploadModel.cs b/Teknik/Areas/API/V1/Models/UploadAPIv1Model.cs similarity index 86% rename from Teknik/Areas/API/Models/APIv1UploadModel.cs rename to Teknik/Areas/API/V1/Models/UploadAPIv1Model.cs index 57bf3fe..8b06c60 100644 --- a/Teknik/Areas/API/Models/APIv1UploadModel.cs +++ b/Teknik/Areas/API/V1/Models/UploadAPIv1Model.cs @@ -4,9 +4,9 @@ using System.Collections.Generic; using System.Linq; using System.Web; -namespace Teknik.Areas.API.Models +namespace Teknik.Areas.API.V1.Models { - public class APIv1UploadModel : APIv1BaseModel + public class UploadAPIv1Model : BaseAPIv1Model { public IFormFile file { get; set; } @@ -26,7 +26,7 @@ namespace Teknik.Areas.API.Models public bool genDeletionKey { get; set; } - public APIv1UploadModel() + public UploadAPIv1Model() { file = null; contentType = null; diff --git a/Teknik/Areas/About/Controllers/AboutController.cs b/Teknik/Areas/About/Controllers/AboutController.cs index 53466a1..a3a7955 100644 --- a/Teknik/Areas/About/Controllers/AboutController.cs +++ b/Teknik/Areas/About/Controllers/AboutController.cs @@ -15,7 +15,7 @@ using Teknik.Logging; namespace Teknik.Areas.About.Controllers { - [TeknikAuthorize] + [Authorize] [Area("About")] public class AboutController : DefaultController { diff --git a/Teknik/Areas/Abuse/Controllers/AbuseController.cs b/Teknik/Areas/Abuse/Controllers/AbuseController.cs index 4ca22fe..46c3c8a 100644 --- a/Teknik/Areas/Abuse/Controllers/AbuseController.cs +++ b/Teknik/Areas/Abuse/Controllers/AbuseController.cs @@ -15,7 +15,7 @@ using Teknik.Logging; namespace Teknik.Areas.Abuse.Controllers { - [TeknikAuthorize] + [Authorize] [Area("Abuse")] public class AbuseController : DefaultController { diff --git a/Teknik/Areas/Accounts/Controllers/AccountsController.cs b/Teknik/Areas/Accounts/Controllers/AccountsController.cs deleted file mode 100644 index c6560ef..0000000 --- a/Teknik/Areas/Accounts/Controllers/AccountsController.cs +++ /dev/null @@ -1,285 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Threading.Tasks; -using IdentityServer4.Events; -using IdentityServer4.Extensions; -using IdentityServer4.Services; -using IdentityServer4.Stores; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Teknik.Areas.Accounts.ViewModels; -using Teknik.Areas.Users.Models; -using Teknik.Areas.Users.Utility; -using Teknik.Configuration; -using Teknik.Controllers; -using Teknik.Data; -using Teknik.Logging; -using Teknik.Security; -using Teknik.Utilities; -using TwoStepsAuthenticator; - -namespace Teknik.Areas.Accounts.Controllers -{ - [Area("Accounts")] - public class AccountsController : DefaultController - { - private readonly UserStore _users; - private readonly SignInManager _signInManager; - private readonly UserManager _userManager; - private readonly IIdentityServerInteractionService _interaction; - private readonly IClientStore _clientStore; - private readonly IEventService _events; - - private static readonly UsedCodesManager _usedCodesManager = new UsedCodesManager(); - - private const string _AuthSessionKey = "AuthenticatedUser"; - - public AccountsController( - ILogger logger, - Config config, - TeknikEntities dbContext, - SignInManager signInManager, - UserManager userManager, - IIdentityServerInteractionService interaction, - IClientStore clientStore, - IEventService events, - UserStore users = null) - : base(logger, config, dbContext) - { - _users = users ?? new UserStore(_dbContext, _config); - - _signInManager = signInManager; - _userManager = userManager; - _interaction = interaction; - _clientStore = clientStore; - _events = events; - } - - - [HttpGet] - [AllowAnonymous] - public IActionResult Login(string ReturnUrl) - { - LoginViewModel model = new LoginViewModel(); - model.ReturnUrl = ReturnUrl; - - return View("/Areas/Accounts/Views/Accounts/ViewLogin.cshtml", model); - } - - [HttpPost] - [AllowAnonymous] - public async Task Login([Bind(Prefix = "Login")]LoginViewModel model) - { - var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); - - if (ModelState.IsValid) - { - string username = model.Username; - User user = UserHelper.GetUser(_dbContext, username); - if (user != null) - { - // Make sure they aren't banned or anything - if (user.AccountStatus == AccountStatus.Banned) - { - model.Error = true; - model.ErrorMessage = "Account has been banned."; - - // Raise the error event - await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, model.ErrorMessage)); - - return GenerateActionResult(new { error = model.ErrorMessage }, View("/Areas/Accounts/Views/Accounts/ViewLogin.cshtml", model)); - } - - // Try to sign them in - var valid = await _userManager.CheckPasswordAsync(user, model.Password); - if (valid) - { - // Perform transfer actions on the account - UserHelper.TransferUser(_dbContext, _config, user, model.Password); - user.LastSeen = DateTime.Now; - _dbContext.Entry(user).State = EntityState.Modified; - _dbContext.SaveChanges(); - - // Let's double check their email and git accounts to make sure they exist - string email = UserHelper.GetUserEmailAddress(_config, username); - if (_config.EmailConfig.Enabled && !UserHelper.UserEmailExists(_config, email)) - { - UserHelper.AddUserEmail(_config, email, model.Password); - } - - if (_config.GitConfig.Enabled && !UserHelper.UserGitExists(_config, username)) - { - UserHelper.AddUserGit(_config, username, model.Password); - } - - bool twoFactor = false; - string returnUrl = model.ReturnUrl; - if (user.SecuritySettings.TwoFactorEnabled) - { - twoFactor = true; - // We need to check their device, and two factor them - if (user.SecuritySettings.AllowTrustedDevices) - { - // Check for the trusted device cookie - string token = Request.Cookies[Constants.TRUSTEDDEVICECOOKIE + "_" + username]; - if (!string.IsNullOrEmpty(token)) - { - if (user.TrustedDevices.Where(d => d.Token == token).FirstOrDefault() != null) - { - // The device token is attached to the user, let's let it slide - twoFactor = false; - } - } - } - } - - if (twoFactor) - { - HttpContext.Session.Set(_AuthSessionKey, user.Username); - if (string.IsNullOrEmpty(model.ReturnUrl)) - returnUrl = Request.Headers["Referer"].ToString(); - returnUrl = Url.SubRouteUrl("accounts", "Accounts.CheckAuthenticatorCode", new { returnUrl = returnUrl, rememberMe = model.RememberMe }); - model.ReturnUrl = string.Empty; - } - else - { - returnUrl = Request.Headers["Referer"].ToString(); - - // They don't need two factor auth. - await SignInUser(user, (string.IsNullOrEmpty(model.ReturnUrl)) ? returnUrl : model.ReturnUrl, model.RememberMe); - } - - if (string.IsNullOrEmpty(model.ReturnUrl)) - { - return GenerateActionResult(new { result = returnUrl }, Redirect(returnUrl)); - } - else - { - return Redirect(model.ReturnUrl); - } - } - } - } - // Raise the error event - await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials")); - - model.Error = true; - model.ErrorMessage = "Invalid Username or Password."; - - return GenerateActionResult(new { error = model.ErrorMessage }, View("/Areas/Accounts/Views/Accounts/ViewLogin.cshtml", model)); - } - - /// - /// Handle logout page postback - /// - public async Task Logout() - { - await LogoutUser(User, HttpContext, _signInManager, _events); - - return Redirect(Url.SubRouteUrl("www", "Home.Index")); - } - - [HttpGet] - [AllowAnonymous] - public IActionResult ConfirmTwoFactorAuth(string returnUrl, bool rememberMe) - { - string username = HttpContext.Session.Get(_AuthSessionKey); - if (!string.IsNullOrEmpty(username)) - { - User user = UserHelper.GetUser(_dbContext, username); - ViewBag.Title = "Unknown Device - " + _config.Title; - ViewBag.Description = "We do not recognize this device."; - TwoFactorViewModel model = new TwoFactorViewModel(); - model.ReturnUrl = returnUrl; - model.RememberMe = rememberMe; - model.AllowTrustedDevice = user.SecuritySettings.AllowTrustedDevices; - - return View("/Areas/Accounts/Views/Accounts/TwoFactorCheck.cshtml", model); - } - return Redirect(Url.SubRouteUrl("error", "Error.Http403")); - } - - [HttpPost] - [AllowAnonymous] - public async Task ConfirmAuthenticatorCode(string code, string returnUrl, bool rememberMe, bool rememberDevice, string deviceName) - { - string errorMessage = string.Empty; - string username = HttpContext.Session.Get(_AuthSessionKey); - if (!string.IsNullOrEmpty(username)) - { - User user = UserHelper.GetUser(_dbContext, username); - if (user.SecuritySettings.TwoFactorEnabled) - { - string key = user.SecuritySettings.TwoFactorKey; - - TimeAuthenticator ta = new TimeAuthenticator(usedCodeManager: _usedCodesManager); - bool isValid = ta.CheckCode(key, code, user); - - if (isValid) - { - // the code was valid, let's log them in! - await SignInUser(user, returnUrl, rememberMe); - - if (user.SecuritySettings.AllowTrustedDevices && rememberDevice) - { - // They want to remember the device, and have allow trusted devices on - var cookieOptions = UserHelper.CreateTrustedDeviceCookie(_config, user.Username, Request.Host.Host.GetDomain(), Request.IsLocal()); - Response.Cookies.Append(Constants.TRUSTEDDEVICECOOKIE + "_" + username, cookieOptions.Item2, cookieOptions.Item1); - - TrustedDevice device = new TrustedDevice(); - device.UserId = user.UserId; - device.Name = (string.IsNullOrEmpty(deviceName)) ? "Unknown" : deviceName; - device.DateSeen = DateTime.Now; - device.Token = cookieOptions.Item2; - - // Add the token - _dbContext.TrustedDevices.Add(device); - _dbContext.SaveChanges(); - } - - if (string.IsNullOrEmpty(returnUrl)) - returnUrl = Request.Headers["Referer"].ToString(); - return Json(new { result = returnUrl }); - } - errorMessage = "Invalid Authentication Code" ; - } - errorMessage = "User does not have Two Factor Authentication enabled"; - } - errorMessage = "User does not exist"; - - // Raise the error event - await _events.RaiseAsync(new UserLoginFailureEvent(username, errorMessage)); - return Json(new { error = errorMessage }); - } - - public async Task SignInUser(User user, string returnUrl, bool rememberMe) - { - // Sign In with Identity - await _signInManager.SignInAsync(user, rememberMe); - - // Sign in via Identity Server - await _events.RaiseAsync(new UserLoginSuccessEvent(user.Username, user.UserId.ToString(), user.Username)); - } - - public static async Task LogoutUser(ClaimsPrincipal user, HttpContext context, SignInManager signInManager, IEventService eventService) - { - if (user?.Identity.IsAuthenticated == true) - { - // delete local authentication cookie - await signInManager.SignOutAsync(); - - // raise the logout event - await eventService.RaiseAsync(new UserLogoutSuccessEvent(user.GetSubjectId(), user.GetDisplayName())); - } - } - - } -} \ No newline at end of file diff --git a/Teknik/Areas/Accounts/ViewModels/LoginViewModel.cs b/Teknik/Areas/Accounts/ViewModels/LoginViewModel.cs deleted file mode 100644 index f7c8f3a..0000000 --- a/Teknik/Areas/Accounts/ViewModels/LoginViewModel.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using Teknik.ViewModels; - -namespace Teknik.Areas.Accounts.ViewModels -{ - public class LoginViewModel : ViewModelBase - { - [Required] - [Display(Name = "Username")] - public string Username { get; set; } - - [Required] - [Display(Name = "Password")] - [DataType(DataType.Password)] - public string Password { get; set; } - - [Display(Name = "Remember Me")] - public bool RememberMe { get; set; } - - public string ReturnUrl { get; set; } - } -} \ No newline at end of file diff --git a/Teknik/Areas/Accounts/ViewModels/TwoFactorViewModel.cs b/Teknik/Areas/Accounts/ViewModels/TwoFactorViewModel.cs deleted file mode 100644 index 0041152..0000000 --- a/Teknik/Areas/Accounts/ViewModels/TwoFactorViewModel.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using Teknik.ViewModels; - -namespace Teknik.Areas.Accounts.ViewModels -{ - public class TwoFactorViewModel : ViewModelBase - { - public bool RememberMe { get; set; } - - public string ReturnUrl { get; set; } - - public bool AllowTrustedDevice { get; set; } - } -} \ No newline at end of file diff --git a/Teknik/Areas/Accounts/Views/Accounts/Login.cshtml b/Teknik/Areas/Accounts/Views/Accounts/Login.cshtml deleted file mode 100644 index 97c0e7f..0000000 --- a/Teknik/Areas/Accounts/Views/Accounts/Login.cshtml +++ /dev/null @@ -1,40 +0,0 @@ -@model Teknik.Areas.Accounts.ViewModels.LoginViewModel - -@if (Config.UserConfig.LoginEnabled) -{ -
    -
    -
    - @if (Model.Error) - { -
    @Model.ErrorMessage
    - } -
    -
    -
    - - -
    - -
    -
    - -
    -
    - -
    - @if (Config.UserConfig.PasswordResetEnabled) - { -

    Forgotten Password?

    - } -
    - -
    - -} -else -{ -

    User logins have been disabled

    -} diff --git a/Teknik/Areas/Accounts/Views/Accounts/TwoFactorCheck.cshtml b/Teknik/Areas/Accounts/Views/Accounts/TwoFactorCheck.cshtml deleted file mode 100644 index 7d0f6f6..0000000 --- a/Teknik/Areas/Accounts/Views/Accounts/TwoFactorCheck.cshtml +++ /dev/null @@ -1,49 +0,0 @@ -@model Teknik.Areas.Accounts.ViewModels.TwoFactorViewModel - - - -
    -
    -
    -
    -

    Two-factor authentication

    -
    -
    -
    -
    -
    -
    -
    -
    - - -
    - - -
    - @if (Model.AllowTrustedDevice) - { -
    - -
    - Set this device as a trusted device for 30 days. It is not advised to trust a public computer. -
    -
    - } -
    - -
    -

    Open the two-factor authentication app on your device to view your authentication code and verify your identity.

    -
    -
    -
    -
    -
    -
    -
    - - \ No newline at end of file diff --git a/Teknik/Areas/Accounts/Views/Accounts/ViewLogin.cshtml b/Teknik/Areas/Accounts/Views/Accounts/ViewLogin.cshtml deleted file mode 100644 index e8340d1..0000000 --- a/Teknik/Areas/Accounts/Views/Accounts/ViewLogin.cshtml +++ /dev/null @@ -1,14 +0,0 @@ -@model Teknik.Areas.Accounts.ViewModels.LoginViewModel - -
    -
    -
    -
    -

    Teknik Login

    -
    - @await Html.PartialAsync("../../Areas/Accounts/Views/Accounts/Login", Model) -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/Teknik/Areas/Admin/Controllers/AdminController.cs b/Teknik/Areas/Admin/Controllers/AdminController.cs index 4d59524..6125eeb 100644 --- a/Teknik/Areas/Admin/Controllers/AdminController.cs +++ b/Teknik/Areas/Admin/Controllers/AdminController.cs @@ -17,53 +17,59 @@ using Teknik.Models; using Teknik.Utilities; using Teknik.ViewModels; using Teknik.Logging; +using Microsoft.AspNetCore.Authorization; namespace Teknik.Areas.Admin.Controllers { - [TeknikAuthorize(Roles = "Admin")] + [Authorize(Roles = "Admin")] [Area("Admin")] public class AdminController : DefaultController { public AdminController(ILogger logger, Config config, TeknikEntities dbContext) : base (logger, config, dbContext) { } [HttpGet] - public ActionResult Dashboard() + public IActionResult Dashboard() { DashboardViewModel model = new DashboardViewModel(); return View(model); } [HttpGet] - public ActionResult UserSearch() + public IActionResult UserSearch() { UserSearchViewModel model = new UserSearchViewModel(); return View(model); } [HttpGet] - public ActionResult UserInfo(string username) + public async Task UserInfo(string username) { if (UserHelper.UserExists(_dbContext, username)) { User user = UserHelper.GetUser(_dbContext, username); UserInfoViewModel model = new UserInfoViewModel(); model.Username = user.Username; - model.AccountType = user.AccountType; - model.AccountStatus = user.AccountStatus; + + // Get Identity User Info + var info = await IdentityHelper.GetIdentityUserInfo(_config, username); + if (info.AccountType.HasValue) + model.AccountType = info.AccountType.Value; + if (info.AccountStatus.HasValue) + model.AccountStatus = info.AccountStatus.Value; return View(model); } return Redirect(Url.SubRouteUrl("error", "Error.Http404")); } [HttpGet] - public ActionResult UploadSearch() + public IActionResult UploadSearch() { UploadSearchViewModel model = new UploadSearchViewModel(); return View(model); } [HttpPost] - public async Task GetUserSearchResults(string query, [FromServices] ICompositeViewEngine viewEngine) + public async Task GetUserSearchResults(string query, [FromServices] ICompositeViewEngine viewEngine) { List models = new List(); @@ -80,8 +86,11 @@ namespace Teknik.Areas.Admin.Controllers { model.Email = string.Format("{0}@{1}", user.Username, _config.EmailConfig.Domain); } - model.JoinDate = user.JoinDate; - model.LastSeen = UserHelper.GetLastAccountActivity(_dbContext, _config, user); + var info = await IdentityHelper.GetIdentityUserInfo(_config, user.Username); + if (info.CreationDate.HasValue) + model.JoinDate = info.CreationDate.Value; + + model.LastSeen = await UserHelper.GetLastAccountActivity(_dbContext, _config, user.Username); models.Add(model); } catch (Exception) @@ -97,7 +106,7 @@ namespace Teknik.Areas.Admin.Controllers } [HttpPost] - public async Task GetUploadSearchResults(string url, [FromServices] ICompositeViewEngine viewEngine) + public async Task GetUploadSearchResults(string url, [FromServices] ICompositeViewEngine viewEngine) { Upload.Models.Upload foundUpload = _dbContext.Uploads.Where(u => u.Url == url).FirstOrDefault(); if (foundUpload != null) @@ -120,12 +129,12 @@ namespace Teknik.Areas.Admin.Controllers [HttpPost] [ValidateAntiForgeryToken] - public ActionResult EditUserAccountType(string username, AccountType accountType) + public async Task EditUserAccountType(string username, AccountType accountType) { if (UserHelper.UserExists(_dbContext, username)) { // Edit the user's account type - UserHelper.EditAccountType(_dbContext, _config, username, accountType); + await UserHelper.EditAccountType(_dbContext, _config, username, accountType); return Json(new { result = new { success = true } }); } return Redirect(Url.SubRouteUrl("error", "Error.Http404")); @@ -133,12 +142,12 @@ namespace Teknik.Areas.Admin.Controllers [HttpPost] [ValidateAntiForgeryToken] - public ActionResult EditUserAccountStatus(string username, AccountStatus accountStatus) + public async Task EditUserAccountStatus(string username, AccountStatus accountStatus) { if (UserHelper.UserExists(_dbContext, username)) { // Edit the user's account type - UserHelper.EditAccountStatus(_dbContext, _config, username, accountStatus); + await UserHelper.EditAccountStatus(_dbContext, _config, username, accountStatus); return Json(new { result = new { success = true } }); } return Redirect(Url.SubRouteUrl("error", "Error.Http404")); @@ -146,7 +155,7 @@ namespace Teknik.Areas.Admin.Controllers [HttpPost] [ValidateAntiForgeryToken] - public ActionResult CreateInviteCode(string username) + public IActionResult CreateInviteCode(string username) { if (UserHelper.UserExists(_dbContext, username)) { @@ -165,14 +174,14 @@ namespace Teknik.Areas.Admin.Controllers [HttpPost] [ValidateAntiForgeryToken] - public ActionResult DeleteAccount(string username) + public async Task DeleteAccount(string username) { try { User user = UserHelper.GetUser(_dbContext, username); if (user != null) { - UserHelper.DeleteAccount(_dbContext, _config, user); + await UserHelper.DeleteAccount(_dbContext, _config, user); return Json(new { result = true }); } } diff --git a/Teknik/Areas/Blog/Controllers/BlogController.cs b/Teknik/Areas/Blog/Controllers/BlogController.cs index d86dce1..41958b8 100644 --- a/Teknik/Areas/Blog/Controllers/BlogController.cs +++ b/Teknik/Areas/Blog/Controllers/BlogController.cs @@ -23,7 +23,7 @@ using Teknik.Logging; namespace Teknik.Areas.Blog.Controllers { - [TeknikAuthorize] + [Authorize] [Area("Blog")] public class BlogController : DefaultController { diff --git a/Teknik/Areas/Contact/Controllers/ContactController.cs b/Teknik/Areas/Contact/Controllers/ContactController.cs index 1f3f9b9..478ce78 100644 --- a/Teknik/Areas/Contact/Controllers/ContactController.cs +++ b/Teknik/Areas/Contact/Controllers/ContactController.cs @@ -19,7 +19,7 @@ using Teknik.Logging; namespace Teknik.Areas.Contact.Controllers { - [TeknikAuthorize] + [Authorize] [Area("Contact")] public class ContactController : DefaultController { diff --git a/Teknik/Areas/Dev/Controllers/DevController.cs b/Teknik/Areas/Dev/Controllers/DevController.cs index 87c8be9..db7afa7 100644 --- a/Teknik/Areas/Dev/Controllers/DevController.cs +++ b/Teknik/Areas/Dev/Controllers/DevController.cs @@ -11,7 +11,7 @@ using Teknik.Logging; namespace Teknik.Areas.Dev.Controllers { - [TeknikAuthorize] + [Authorize] [Area("Dev")] public class DevController : DefaultController { diff --git a/Teknik/Areas/Error/Controllers/ErrorController.cs b/Teknik/Areas/Error/Controllers/ErrorController.cs index e41db53..122f8b3 100644 --- a/Teknik/Areas/Error/Controllers/ErrorController.cs +++ b/Teknik/Areas/Error/Controllers/ErrorController.cs @@ -21,7 +21,7 @@ using Teknik.Logging; namespace Teknik.Areas.Error.Controllers { - [TeknikAuthorize] + [Authorize] [Area("Error")] public class ErrorController : DefaultController { @@ -105,15 +105,20 @@ namespace Teknik.Areas.Error.Controllers [AllowAnonymous] public IActionResult Http500(Exception exception) { - if (HttpContext != null) + try { - var ex = HttpContext.Features.Get(); - if (ex != null) + if (HttpContext != null) { - exception = ex.Error; + var ex = HttpContext.Features.Get(); + if (ex != null) + { + exception = ex.Error; + } + HttpContext.Session.Set("Exception", exception); } - HttpContext.Session.Set("Exception", exception); } + catch + { } Response.StatusCode = StatusCodes.Status500InternalServerError; diff --git a/Teknik/Areas/FAQ/Controllers/FAQController.cs b/Teknik/Areas/FAQ/Controllers/FAQController.cs index f092c7b..350d81d 100644 --- a/Teknik/Areas/FAQ/Controllers/FAQController.cs +++ b/Teknik/Areas/FAQ/Controllers/FAQController.cs @@ -11,7 +11,7 @@ using Teknik.Logging; namespace Teknik.Areas.FAQ.Controllers { - [TeknikAuthorize] + [Authorize] [Area("FAQ")] public class FAQController : DefaultController { diff --git a/Teknik/Areas/Help/Controllers/HelpController.cs b/Teknik/Areas/Help/Controllers/HelpController.cs index 0cf9255..54eb35f 100644 --- a/Teknik/Areas/Help/Controllers/HelpController.cs +++ b/Teknik/Areas/Help/Controllers/HelpController.cs @@ -11,7 +11,7 @@ using Teknik.Logging; namespace Teknik.Areas.Help.Controllers { - [TeknikAuthorize] + [Authorize] [Area("Help")] public class HelpController : DefaultController { diff --git a/Teknik/Areas/Home/Controllers/HomeController.cs b/Teknik/Areas/Home/Controllers/HomeController.cs index d7d48e2..6cf725a 100644 --- a/Teknik/Areas/Home/Controllers/HomeController.cs +++ b/Teknik/Areas/Home/Controllers/HomeController.cs @@ -16,7 +16,7 @@ using System; namespace Teknik.Areas.Home.Controllers { - [TeknikAuthorize] + [Authorize] [Area("Home")] public class HomeController : DefaultController { diff --git a/Teknik/Areas/Paste/Controllers/PasteController.cs b/Teknik/Areas/Paste/Controllers/PasteController.cs index 812f30b..e4736e6 100644 --- a/Teknik/Areas/Paste/Controllers/PasteController.cs +++ b/Teknik/Areas/Paste/Controllers/PasteController.cs @@ -21,7 +21,7 @@ using Teknik.Logging; namespace Teknik.Areas.Paste.Controllers { - [TeknikAuthorize] + [Authorize] [Area("Paste")] public class PasteController : DefaultController { @@ -84,14 +84,14 @@ namespace Teknik.Areas.Paste.Controllers byte[] passBytes = SHA384.Hash(paste.Key, password); hash = passBytes.ToHex(); // We need to convert old pastes to the new password scheme - if (paste.Transfers.ToList().Exists(t => t.Type == TransferTypes.ASCIIPassword)) - { - hash = Encoding.ASCII.GetString(passBytes); - // Remove the transfer types - paste.Transfers.Clear(); - _dbContext.Entry(paste).State = EntityState.Modified; - _dbContext.SaveChanges(); - } + //if (paste.Transfers.ToList().Exists(t => t.Type == TransferTypes.ASCIIPassword)) + //{ + // hash = Encoding.ASCII.GetString(passBytes); + // // Remove the transfer types + // paste.Transfers.Clear(); + // _dbContext.Entry(paste).State = EntityState.Modified; + // _dbContext.SaveChanges(); + //} } if (string.IsNullOrEmpty(password) || hash != paste.HashedPassword) { @@ -145,6 +145,7 @@ namespace Teknik.Areas.Paste.Controllers [HttpPost] [AllowAnonymous] + [DisableRequestSizeLimit] public IActionResult Paste([Bind("Content, Title, Syntax, ExpireLength, ExpireUnit, Password, Hide")]PasteCreateViewModel model) { if (ModelState.IsValid) diff --git a/Teknik/Areas/Paste/Models/Paste.cs b/Teknik/Areas/Paste/Models/Paste.cs index 2cdc857..bb481cd 100644 --- a/Teknik/Areas/Paste/Models/Paste.cs +++ b/Teknik/Areas/Paste/Models/Paste.cs @@ -50,7 +50,5 @@ namespace Teknik.Areas.Paste.Models public int MaxViews { get; set; } public int Views { get; set; } - - public virtual ICollection Transfers { get; set; } } } diff --git a/Teknik/Areas/Podcast/Controllers/PodcastController.cs b/Teknik/Areas/Podcast/Controllers/PodcastController.cs index 8a37e96..cf296ca 100644 --- a/Teknik/Areas/Podcast/Controllers/PodcastController.cs +++ b/Teknik/Areas/Podcast/Controllers/PodcastController.cs @@ -22,7 +22,7 @@ using Teknik.Logging; namespace Teknik.Areas.Podcast.Controllers { - [TeknikAuthorize] + [Authorize] [Area("Podcast")] public class PodcastController : DefaultController { @@ -535,6 +535,7 @@ namespace Teknik.Areas.Podcast.Controllers } #endregion + [DisableRequestSizeLimit] public async Task> SaveFilesAsync(IFormFileCollection files, int episode) { List podFiles = new List(); diff --git a/Teknik/Areas/Privacy/Controllers/PrivacyController.cs b/Teknik/Areas/Privacy/Controllers/PrivacyController.cs index 849508b..cf946d8 100644 --- a/Teknik/Areas/Privacy/Controllers/PrivacyController.cs +++ b/Teknik/Areas/Privacy/Controllers/PrivacyController.cs @@ -11,7 +11,7 @@ using Teknik.Logging; namespace Teknik.Areas.Privacy.Controllers { - [TeknikAuthorize] + [Authorize] [Area("Privacy")] public class PrivacyController : DefaultController { diff --git a/Teknik/Areas/Shortener/Controllers/ShortenerController.cs b/Teknik/Areas/Shortener/Controllers/ShortenerController.cs index d243b04..8aee69d 100644 --- a/Teknik/Areas/Shortener/Controllers/ShortenerController.cs +++ b/Teknik/Areas/Shortener/Controllers/ShortenerController.cs @@ -16,7 +16,7 @@ using Teknik.Utilities; namespace Teknik.Areas.Shortener.Controllers { - [TeknikAuthorize] + [Authorize] [Area("Shortener")] public class ShortenerController : DefaultController { diff --git a/Teknik/Areas/Stats/Controllers/StatsController.cs b/Teknik/Areas/Stats/Controllers/StatsController.cs index d8bb73f..50a6e21 100644 --- a/Teknik/Areas/Stats/Controllers/StatsController.cs +++ b/Teknik/Areas/Stats/Controllers/StatsController.cs @@ -17,7 +17,7 @@ using Teknik.Utilities; namespace Teknik.Areas.Stats.Controllers { - [TeknikAuthorize] + [Authorize] [Area("Stats")] public class StatsController : DefaultController { diff --git a/Teknik/Areas/TOS/Controllers/TOSController.cs b/Teknik/Areas/TOS/Controllers/TOSController.cs index 73af299..0f25546 100644 --- a/Teknik/Areas/TOS/Controllers/TOSController.cs +++ b/Teknik/Areas/TOS/Controllers/TOSController.cs @@ -11,7 +11,7 @@ using Teknik.Logging; namespace Teknik.Areas.TOS.Controllers { - [TeknikAuthorize] + [Authorize] [Area("TOS")] public class TOSController : DefaultController { diff --git a/Teknik/Areas/Upload/Controllers/UploadController.cs b/Teknik/Areas/Upload/Controllers/UploadController.cs index fcb9abc..0800018 100644 --- a/Teknik/Areas/Upload/Controllers/UploadController.cs +++ b/Teknik/Areas/Upload/Controllers/UploadController.cs @@ -25,7 +25,7 @@ using Teknik.Areas.Users.Models; namespace Teknik.Areas.Upload.Controllers { - [TeknikAuthorize] + [Authorize] [Area("Upload")] public class UploadController : DefaultController { @@ -53,6 +53,7 @@ namespace Teknik.Areas.Upload.Controllers [HttpPost] [AllowAnonymous] + [DisableRequestSizeLimit] public async Task Upload(string fileType, string fileExt, string iv, int keySize, int blockSize, bool encrypt, IFormFile file) { try @@ -64,10 +65,10 @@ namespace Teknik.Areas.Upload.Controllers { maxUploadSize = _config.UploadConfig.MaxUploadSizeBasic; User user = UserHelper.GetUser(_dbContext, User.Identity.Name); - if (user.AccountType == AccountType.Premium) - { - maxUploadSize = _config.UploadConfig.MaxUploadSizePremium; - } + //if (user.AccountType == AccountType.Premium) + //{ + // maxUploadSize = _config.UploadConfig.MaxUploadSizePremium; + //} } if (file.Length <= maxUploadSize) { @@ -170,12 +171,12 @@ namespace Teknik.Areas.Upload.Controllers contentType = uploads.ContentType; contentLength = uploads.ContentLength; dateUploaded = uploads.DateUploaded; - if (User.Identity.IsAuthenticated) - { - User user = UserHelper.GetUser(_dbContext, User.Identity.Name); - premiumAccount = user.AccountType == AccountType.Premium; - } - premiumAccount |= (uploads.User != null && uploads.User.AccountType == AccountType.Premium); + //if (User.Identity.IsAuthenticated) + //{ + // User user = UserHelper.GetUser(_dbContext, User.Identity.Name); + // premiumAccount = user.AccountType == AccountType.Premium; + //} + //premiumAccount |= (uploads.User != null && uploads.User.AccountType == AccountType.Premium); } else { diff --git a/Teknik/Areas/User/Controllers/UserController.cs b/Teknik/Areas/User/Controllers/UserController.cs index 4ed3212..f68b8b2 100644 --- a/Teknik/Areas/User/Controllers/UserController.cs +++ b/Teknik/Areas/User/Controllers/UserController.cs @@ -24,28 +24,147 @@ using Teknik.Logging; using System.Security.Claims; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication; -using Teknik.Areas.Accounts.Controllers; using IdentityServer4.Services; using Microsoft.AspNetCore.Identity; +using IdentityModel.Client; +using System.Net.Http; +using Newtonsoft.Json.Linq; +using Teknik.Security; +using Microsoft.IdentityModel.Tokens; +using IdentityModel; +using System.Security.Cryptography; +using System.IdentityModel.Tokens.Jwt; +using Microsoft.AspNetCore.Http; namespace Teknik.Areas.Users.Controllers { - [TeknikAuthorize] + [Authorize] [Area("User")] public class UserController : DefaultController { private static readonly UsedCodesManager usedCodesManager = new UsedCodesManager(); private const string _AuthSessionKey = "AuthenticatedUser"; - private readonly SignInManager _signInManager; - private readonly IEventService _events; + private readonly IHttpContextAccessor _httpContextAccessor; + private ISession _session => _httpContextAccessor.HttpContext.Session; - public UserController(ILogger logger, Config config, TeknikEntities dbContext, SignInManager signInManager, IEventService eventService) : base(logger, config, dbContext) + public LogoutSessionManager _logoutSessions { get; } + + public UserController(ILogger logger, Config config, TeknikEntities dbContext, LogoutSessionManager logoutSessions, IHttpContextAccessor httpContextAccessor) : base(logger, config, dbContext) { - _signInManager = signInManager; - _events = eventService; + _logoutSessions = logoutSessions; + _httpContextAccessor = httpContextAccessor; } - + + [HttpGet] + public IActionResult Login(string returnUrl) + { + // Let's double check their email and git accounts to make sure they exist + string email = UserHelper.GetUserEmailAddress(_config, User.Identity.Name); + if (_config.EmailConfig.Enabled && !UserHelper.UserEmailExists(_config, email)) + { + //UserHelper.AddUserEmail(_config, email, model.Password); + } + + if (_config.GitConfig.Enabled && !UserHelper.UserGitExists(_config, User.Identity.Name)) + { + //UserHelper.AddUserGit(_config, User.Identity.Name, model.Password); + } + + if (!string.IsNullOrEmpty(returnUrl)) + { + return Redirect(returnUrl); + } + + return Redirect(Url.SubRouteUrl("www", "Home.Index")); + } + + [HttpGet] + public async Task Logout() + { + await HttpContext.SignOutAsync("Cookies"); + await HttpContext.SignOutAsync("oidc"); + } + + [HttpPost] + [AllowAnonymous] + public async Task Logout(string logout_token) + { + Response.Headers.Add("Cache-Control", "no-cache, no-store"); + Response.Headers.Add("Pragma", "no-cache"); + + try + { + var user = await ValidateLogoutToken(logout_token); + + // these are the sub & sid to signout + var sub = user.FindFirst("sub")?.Value; + var sid = user.FindFirst("sid")?.Value; + + _logoutSessions.Add(sub, sid); + + return Ok(); + } + catch { } + + return BadRequest(); + } + + private async Task ValidateLogoutToken(string logoutToken) + { + var claims = await ValidateJwt(logoutToken); + + if (claims.FindFirst("sub") == null && claims.FindFirst("sid") == null) throw new Exception("Invalid logout token"); + + var nonce = claims.FindFirstValue("nonce"); + if (!String.IsNullOrWhiteSpace(nonce)) throw new Exception("Invalid logout token"); + + var eventsJson = claims.FindFirst("events")?.Value; + if (String.IsNullOrWhiteSpace(eventsJson)) throw new Exception("Invalid logout token"); + + var events = JObject.Parse(eventsJson); + var logoutEvent = events.TryGetValue("http://schemas.openid.net/event/backchannel-logout"); + if (logoutEvent == null) throw new Exception("Invalid logout token"); + + return claims; + } + + private async Task ValidateJwt(string jwt) + { + // read discovery document to find issuer and key material + var disco = await DiscoveryClient.GetAsync(_config.UserConfig.IdentityServerConfig.Authority); + + var keys = new List(); + foreach (var webKey in disco.KeySet.Keys) + { + var e = Base64Url.Decode(webKey.E); + var n = Base64Url.Decode(webKey.N); + + var key = new RsaSecurityKey(new RSAParameters { Exponent = e, Modulus = n }) + { + KeyId = webKey.Kid + }; + + keys.Add(key); + } + + var parameters = new TokenValidationParameters + { + ValidIssuer = disco.Issuer, + ValidAudience = _config.UserConfig.IdentityServerConfig.ClientId, + IssuerSigningKeys = keys, + + NameClaimType = JwtClaimTypes.Name, + RoleClaimType = JwtClaimTypes.Role + }; + + var handler = new JwtSecurityTokenHandler(); + handler.InboundClaimTypeMap.Clear(); + + var user = handler.ValidateToken(jwt, parameters, out var _); + return user; + } + [AllowAnonymous] public IActionResult GetPremium() { @@ -82,7 +201,7 @@ namespace Teknik.Areas.Users.Controllers model.Error = true; model.ErrorMessage = "That username is not valid"; } - if (!model.Error && !UserHelper.UsernameAvailable(_dbContext, _config, model.Username)) + if (!model.Error && !(await UserHelper.UsernameAvailable(_dbContext, _config, model.Username))) { model.Error = true; model.ErrorMessage = "That username is not available"; @@ -105,50 +224,11 @@ namespace Teknik.Areas.Users.Controllers model.ErrorMessage = "Invalid Invite Code"; } - // PGP Key valid? - if (!model.Error && !string.IsNullOrEmpty(model.PublicKey) && !PGP.IsPublicKey(model.PublicKey)) - { - model.Error = true; - model.ErrorMessage = "Invalid PGP Public Key"; - } - if (!model.Error) { try { - User newUser = new User(); - newUser.JoinDate = DateTime.Now; - newUser.Username = model.Username; - newUser.UserSettings = new UserSettings(); - newUser.SecuritySettings = new SecuritySettings(); - newUser.BlogSettings = new BlogSettings(); - newUser.UploadSettings = new UploadSettings(); - - if (!string.IsNullOrEmpty(model.PublicKey)) - newUser.SecuritySettings.PGPSignature = model.PublicKey; - if (!string.IsNullOrEmpty(model.RecoveryEmail)) - newUser.SecuritySettings.RecoveryEmail = model.RecoveryEmail; - - // if they provided an invite code, let's assign them to it - if (!string.IsNullOrEmpty(model.InviteCode)) - { - InviteCode code = _dbContext.InviteCodes.Where(c => c.Code == model.InviteCode).FirstOrDefault(); - _dbContext.Entry(code).State = EntityState.Modified; - _dbContext.SaveChanges(); - - newUser.ClaimedInviteCode = code; - } - - UserHelper.AddAccount(_dbContext, _config, newUser, model.Password); - - // If they have a recovery email, let's send a verification - if (!string.IsNullOrEmpty(model.RecoveryEmail)) - { - string verifyCode = UserHelper.CreateRecoveryEmailVerification(_dbContext, _config, newUser); - string resetUrl = Url.SubRouteUrl("user", "User.ResetPassword", new { Username = model.Username }); - string verifyUrl = Url.SubRouteUrl("user", "User.VerifyRecoveryEmail", new { Code = verifyCode }); - UserHelper.SendRecoveryEmailVerification(_config, model.Username, model.RecoveryEmail, resetUrl, verifyUrl); - } + await UserHelper.CreateAccount(_dbContext, _config, Url, model.Username, model.Password, model.RecoveryEmail, model.InviteCode); } catch (Exception ex) { @@ -157,7 +237,7 @@ namespace Teknik.Areas.Users.Controllers } if (!model.Error) { - return Redirect(Url.SubRouteUrl("accounts", "Accounts.Login", new { ReturnUrl = model.ReturnUrl })); + return Redirect(Url.SubRouteUrl("user", "User.Login", new { returnUrl = model.ReturnUrl })); } } } @@ -177,7 +257,7 @@ namespace Teknik.Areas.Users.Controllers // GET: Profile/Profile [AllowAnonymous] - public IActionResult ViewProfile(string username) + public async Task ViewProfile(string username) { if (string.IsNullOrEmpty(username)) { @@ -203,13 +283,13 @@ namespace Teknik.Areas.Users.Controllers { model.Email = string.Format("{0}@{1}", user.Username, _config.EmailConfig.Domain); } - model.JoinDate = user.JoinDate; - model.LastSeen = UserHelper.GetLastAccountActivity(_dbContext, _config, user); - model.AccountType = user.AccountType; - model.AccountStatus = user.AccountStatus; + + // Get the user claims for this user + model.IdentityUserInfo = await IdentityHelper.GetIdentityUserInfo(_config, user.Username); + + model.LastSeen = UserHelper.GetLastAccountActivity(_dbContext, _config, user.Username, model.IdentityUserInfo); model.UserSettings = user.UserSettings; - model.SecuritySettings = user.SecuritySettings; model.BlogSettings = user.BlogSettings; model.UploadSettings = user.UploadSettings; @@ -236,7 +316,7 @@ namespace Teknik.Areas.Users.Controllers public IActionResult Settings() { - return Redirect(Url.SubRouteUrl("user", "User.SecuritySettings")); + return Redirect(Url.SubRouteUrl("user", "User.ProfileSettings")); } public IActionResult ProfileSettings() @@ -246,8 +326,6 @@ namespace Teknik.Areas.Users.Controllers if (user != null) { - HttpContext.Session.Set(_AuthSessionKey, user.Username); - ViewBag.Title = "Profile Settings - " + _config.Title; ViewBag.Description = "Your " + _config.Title + " Profile Settings"; @@ -264,16 +342,35 @@ namespace Teknik.Areas.Users.Controllers return Redirect(Url.SubRouteUrl("error", "Error.Http403")); } - - public IActionResult SecuritySettings() + + public IActionResult AccountSettings() { string username = User.Identity.Name; User user = UserHelper.GetUser(_dbContext, username); if (user != null) { - HttpContext.Session.Set(_AuthSessionKey, user.Username); + ViewBag.Title = "Account Settings - " + _config.Title; + ViewBag.Description = "Your " + _config.Title + " Account Settings"; + AccountSettingsViewModel model = new AccountSettingsViewModel(); + model.Page = "Account"; + model.UserID = user.UserId; + model.Username = user.Username; + + return View("/Areas/User/Views/User/Settings/AccountSettings.cshtml", model); + } + + return Redirect(Url.SubRouteUrl("error", "Error.Http403")); + } + + public async Task SecuritySettings() + { + string username = User.Identity.Name; + User user = UserHelper.GetUser(_dbContext, username); + + if (user != null) + { ViewBag.Title = "Security Settings - " + _config.Title; ViewBag.Description = "Your " + _config.Title + " Security Settings"; @@ -281,31 +378,66 @@ namespace Teknik.Areas.Users.Controllers model.Page = "Security"; model.UserID = user.UserId; model.Username = user.Username; - model.TrustedDeviceCount = user.TrustedDevices.Count; - model.AuthTokens = new List(); - foreach (AuthToken token in user.AuthTokens) - { - AuthTokenViewModel tokenModel = new AuthTokenViewModel(); - tokenModel.AuthTokenId = token.AuthTokenId; - tokenModel.Name = token.Name; - tokenModel.LastDateUsed = token.LastDateUsed; - model.AuthTokens.Add(tokenModel); - } + // Get the user secure info + IdentityUserInfo userInfo = await IdentityHelper.GetIdentityUserInfo(_config, user.Username); + //model.TrustedDeviceCount = user.TrustedDevices.Count; + //model.AuthTokens = new List(); + //foreach (AuthToken token in user.AuthTokens) + //{ + // AuthTokenViewModel tokenModel = new AuthTokenViewModel(); + // tokenModel.AuthTokenId = token.AuthTokenId; + // tokenModel.Name = token.Name; + // tokenModel.LastDateUsed = token.LastDateUsed; - model.PgpPublicKey = user.SecuritySettings.PGPSignature; - model.RecoveryEmail = user.SecuritySettings.RecoveryEmail; - model.RecoveryVerified = user.SecuritySettings.RecoveryVerified; - model.AllowTrustedDevices = user.SecuritySettings.AllowTrustedDevices; - model.TwoFactorEnabled = user.SecuritySettings.TwoFactorEnabled; - model.TwoFactorKey = user.SecuritySettings.TwoFactorKey; + // model.AuthTokens.Add(tokenModel); + //} + model.PgpPublicKey = userInfo.PGPPublicKey; + model.RecoveryEmail = userInfo.RecoveryEmail; + if (userInfo.RecoveryVerified.HasValue) + model.RecoveryVerified = userInfo.RecoveryVerified.Value; + if (userInfo.TwoFactorEnabled.HasValue) + model.TwoFactorEnabled = userInfo.TwoFactorEnabled.Value; + return View("/Areas/User/Views/User/Settings/SecuritySettings.cshtml", model); } return Redirect(Url.SubRouteUrl("error", "Error.Http403")); } - + + public IActionResult AccessTokenSettings() + { + string username = User.Identity.Name; + User user = UserHelper.GetUser(_dbContext, username); + + if (user != null) + { + ViewBag.Title = "Access Token Settings - " + _config.Title; + ViewBag.Description = "Your " + _config.Title + " Access Token Settings"; + + APIClientSettingsViewModel model = new APIClientSettingsViewModel(); + model.Page = "AccessTokens"; + model.UserID = user.UserId; + model.Username = user.Username; + + model.AuthTokens = new List(); + //foreach (AuthToken token in user.AuthTokens) + //{ + // AuthTokenViewModel tokenModel = new AuthTokenViewModel(); + // tokenModel.AuthTokenId = token.AuthTokenId; + // tokenModel.Name = token.Name; + // tokenModel.LastDateUsed = token.LastDateUsed; + + // model.AuthTokens.Add(tokenModel); + //} + + return View("/Areas/User/Views/User/Settings/AccessTokenSettings.cshtml", model); + } + + return Redirect(Url.SubRouteUrl("error", "Error.Http403")); + } + public IActionResult InviteSettings() { string username = User.Identity.Name; @@ -313,8 +445,6 @@ namespace Teknik.Areas.Users.Controllers if (user != null) { - HttpContext.Session.Set(_AuthSessionKey, user.Username); - ViewBag.Title = "Invite Settings - " + _config.Title; ViewBag.Description = "Your " + _config.Title + " Invite Settings"; @@ -360,8 +490,6 @@ namespace Teknik.Areas.Users.Controllers if (user != null) { - HttpContext.Session.Set(_AuthSessionKey, user.Username); - ViewBag.Title = "Blog Settings - " + _config.Title; ViewBag.Description = "Your " + _config.Title + " Blog Settings"; @@ -385,8 +513,6 @@ namespace Teknik.Areas.Users.Controllers if (user != null) { - HttpContext.Session.Set(_AuthSessionKey, user.Username); - ViewBag.Title = "Upload Settings - " + _config.Title; ViewBag.Description = "Your " + _config.Title + " Upload Settings"; @@ -404,18 +530,15 @@ namespace Teknik.Areas.Users.Controllers [HttpGet] [AllowAnonymous] - public IActionResult ViewRawPGP(string username) + public async Task ViewRawPGP(string username) { ViewBag.Title = username + "'s Public Key - " + _config.Title; ViewBag.Description = "The PGP public key for " + username; - - User user = UserHelper.GetUser(_dbContext, username); - if (user != null) + + IdentityUserInfo userClaims = await IdentityHelper.GetIdentityUserInfo(_config, username); + if (!string.IsNullOrEmpty(userClaims.PGPPublicKey)) { - if (!string.IsNullOrEmpty(user.SecuritySettings.PGPSignature)) - { - return Content(user.SecuritySettings.PGPSignature, "text/plain"); - } + return Content(userClaims.PGPPublicKey, "text/plain"); } return Redirect(Url.SubRouteUrl("error", "Error.Http404")); } @@ -479,7 +602,7 @@ namespace Teknik.Areas.Users.Controllers [HttpPost] [ValidateAntiForgeryToken] - public IActionResult EditSecurity(SecuritySettingsViewModel settings) + public async Task EditSecurity(SecuritySettingsViewModel settings) { if (ModelState.IsValid) { @@ -488,108 +611,49 @@ namespace Teknik.Areas.Users.Controllers User user = UserHelper.GetUser(_dbContext, User.Identity.Name); if (user != null) { - bool changePass = false; - // Changing Password? - if (!string.IsNullOrEmpty(settings.CurrentPassword) && (!string.IsNullOrEmpty(settings.NewPassword) || !string.IsNullOrEmpty(settings.NewPasswordConfirm))) - { - // Old Password Valid? - if (!UserHelper.UserPasswordCorrect(_dbContext, _config, user, settings.CurrentPassword)) - { - return Json(new { error = "Invalid Original Password." }); - } - // The New Password Match? - if (settings.NewPassword != settings.NewPasswordConfirm) - { - return Json(new { error = "New Password Must Match." }); - } - // Are password resets enabled? - if (!_config.UserConfig.PasswordResetEnabled) - { - return Json(new { error = "Password resets are disabled." }); - } - changePass = true; - } - // PGP Key valid? if (!string.IsNullOrEmpty(settings.PgpPublicKey) && !PGP.IsPublicKey(settings.PgpPublicKey)) { return Json(new { error = "Invalid PGP Public Key" }); } - user.SecuritySettings.PGPSignature = settings.PgpPublicKey; - // Recovery Email - bool newRecovery = false; - if (settings.RecoveryEmail != user.SecuritySettings.RecoveryEmail) + // Get the user secure info + IdentityUserInfo userInfo = await IdentityHelper.GetIdentityUserInfo(_config, user.Username); + + if (userInfo.PGPPublicKey != settings.PgpPublicKey) { - newRecovery = true; - user.SecuritySettings.RecoveryEmail = settings.RecoveryEmail; - user.SecuritySettings.RecoveryVerified = false; + var result = await IdentityHelper.UpdatePGPPublicKey(_config, user.Username, settings.PgpPublicKey); + if (!result.Success) + return Json(new { error = result.Message }); } - // Trusted Devices - user.SecuritySettings.AllowTrustedDevices = settings.AllowTrustedDevices; - if (!settings.AllowTrustedDevices) + if (userInfo.RecoveryEmail != settings.RecoveryEmail) { - // They turned it off, let's clear the trusted devices - user.TrustedDevices.Clear(); - List foundDevices = _dbContext.TrustedDevices.Where(d => d.UserId == user.UserId).ToList(); - if (foundDevices != null) + var token = await IdentityHelper.UpdateRecoveryEmail(_config, user.Username, settings.RecoveryEmail); + + // If they have a recovery email, let's send a verification + if (!string.IsNullOrEmpty(settings.RecoveryEmail)) { - foreach (TrustedDevice device in foundDevices) - { - _dbContext.TrustedDevices.Remove(device); - } + string resetUrl = Url.SubRouteUrl("user", "User.ResetPassword", new { Username = user.Username }); + string verifyUrl = Url.SubRouteUrl("user", "User.VerifyRecoveryEmail", new { Username = user.Username, Code = WebUtility.UrlEncode(token) }); + UserHelper.SendRecoveryEmailVerification(_config, user.Username, settings.RecoveryEmail, resetUrl, verifyUrl); } } - // Two Factor Authentication - bool oldTwoFactor = user.SecuritySettings.TwoFactorEnabled; - user.SecuritySettings.TwoFactorEnabled = settings.TwoFactorEnabled; - string newKey = string.Empty; - if (!oldTwoFactor && settings.TwoFactorEnabled) - { - // They just enabled it, let's regen the key - newKey = Authenticator.GenerateKey(); + //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 }); + //} - // New key, so let's upsert their key into git - if (_config.GitConfig.Enabled) - { - UserHelper.CreateUserGitTwoFactor(_config, user.Username, newKey, DateTimeHelper.GetUnixTimestamp()); - } - } - else if (!settings.TwoFactorEnabled) - { - // remove the key when it's disabled - newKey = string.Empty; + //UserHelper.EditAccount(_dbContext, _config, user, changePass, settings.NewPassword); - // Removed the key, so delete it from git as well - if (_config.GitConfig.Enabled) - { - UserHelper.DeleteUserGitTwoFactor(_config, user.Username); - } - } - else - { - // No change, let's use the old value - newKey = user.SecuritySettings.TwoFactorKey; - } - user.SecuritySettings.TwoFactorKey = newKey; - UserHelper.EditAccount(_dbContext, _config, user, changePass, settings.NewPassword); - - // If they have a recovery email, let's send a verification - if (!string.IsNullOrEmpty(settings.RecoveryEmail) && newRecovery) - { - string verifyCode = UserHelper.CreateRecoveryEmailVerification(_dbContext, _config, user); - string resetUrl = Url.SubRouteUrl("user", "User.ResetPassword", new { Username = user.Username }); - string verifyUrl = Url.SubRouteUrl("user", "User.VerifyRecoveryEmail", new { Code = verifyCode }); - UserHelper.SendRecoveryEmailVerification(_config, user.Username, user.SecuritySettings.RecoveryEmail, resetUrl, verifyUrl); - } - - if (!oldTwoFactor && settings.TwoFactorEnabled) - { - return Json(new { result = new { checkAuth = true, key = newKey, qrUrl = Url.SubRouteUrl("user", "User.Action", new { action = "GenerateAuthQrCode", key = newKey }) } }); - } + //if (!oldTwoFactor && settings.TwoFactorEnabled) + //{ + // return Json(new { result = new { checkAuth = true, key = newKey, qrUrl = Url.SubRouteUrl("user", "User.Action", new { action = "GenerateAuthQrCode", key = newKey }) } }); + //} return Json(new { result = true }); } return Json(new { error = "User does not exist" }); @@ -629,6 +693,47 @@ namespace Teknik.Areas.Users.Controllers return Json(new { error = "Invalid Parameters" }); } + public async Task ChangePassword(AccountSettingsViewModel settings) + { + + if (ModelState.IsValid) + { + try + { + User user = UserHelper.GetUser(_dbContext, User.Identity.Name); + if (user != null) + { + // Did they enter their old password + if (string.IsNullOrEmpty(settings.CurrentPassword)) + return Json(new { error = "You must enter your current password" }); + // Did they enter a new password + if (string.IsNullOrEmpty(settings.NewPassword) || string.IsNullOrEmpty(settings.NewPasswordConfirm)) + return Json(new { error = "You must enter your new password" }); + // Old Password Valid? + if (!(await UserHelper.UserPasswordCorrect(_config, user.Username, settings.CurrentPassword))) + return Json(new { error = "Invalid Original Password" }); + // The New Password Match? + if (settings.NewPassword != settings.NewPasswordConfirm) + return Json(new { error = "New Password must match confirmation" }); + // Are password resets enabled? + if (!_config.UserConfig.PasswordResetEnabled) + return Json(new { error = "Password resets are disabled" }); + + // Change their password + await UserHelper.ChangeAccountPassword(_dbContext, _config, user.Username, settings.CurrentPassword, settings.NewPassword); + + 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 { error = "Invalid Parameters" }); + } + [HttpPost] [ValidateAntiForgeryToken] public async Task Delete() @@ -640,10 +745,11 @@ namespace Teknik.Areas.Users.Controllers User user = UserHelper.GetUser(_dbContext, User.Identity.Name); if (user != null) { - UserHelper.DeleteAccount(_dbContext, _config, user); + await UserHelper.DeleteAccount(_dbContext, _config, user); // Sign Out - await AccountsController.LogoutUser(User, HttpContext, _signInManager, _events); + await HttpContext.SignOutAsync("Cookies"); + await HttpContext.SignOutAsync("oidc"); return Json(new { result = true }); } @@ -657,7 +763,7 @@ namespace Teknik.Areas.Users.Controllers } [HttpGet] - public IActionResult VerifyRecoveryEmail(string code) + public async Task VerifyRecoveryEmail(string username, string code) { bool verified = true; if (string.IsNullOrEmpty(code)) @@ -666,7 +772,8 @@ namespace Teknik.Areas.Users.Controllers // Is there a code? if (verified) { - verified &= UserHelper.VerifyRecoveryEmail(_dbContext, _config, User.Identity.Name, code); + var result = await IdentityHelper.VerifyRecoveryEmail(_config, username, WebUtility.UrlDecode(code)); + verified &= result.Success; } RecoveryEmailVerificationViewModel model = new RecoveryEmailVerificationViewModel(); @@ -677,24 +784,25 @@ namespace Teknik.Areas.Users.Controllers [HttpPost] [ValidateAntiForgeryToken] - public IActionResult ResendVerifyRecoveryEmail() + public async Task ResendVerifyRecoveryEmail() { if (ModelState.IsValid) { try { + IdentityUserInfo userInfo = await IdentityHelper.GetIdentityUserInfo(_config, User.Identity.Name); User user = UserHelper.GetUser(_dbContext, User.Identity.Name); if (user != null) { - // If they have a recovery email, let's send a verification - if (!string.IsNullOrEmpty(user.SecuritySettings.RecoveryEmail)) + //If they have a recovery email, let's send a verification + if (!string.IsNullOrEmpty(userInfo.RecoveryEmail)) { - if (!user.SecuritySettings.RecoveryVerified) + if (!userInfo.RecoveryVerified.HasValue || !userInfo.RecoveryVerified.Value) { - string verifyCode = UserHelper.CreateRecoveryEmailVerification(_dbContext, _config, user); + var token = await IdentityHelper.UpdateRecoveryEmail(_config, user.Username, userInfo.RecoveryEmail); string resetUrl = Url.SubRouteUrl("user", "User.ResetPassword", new { Username = user.Username }); - string verifyUrl = Url.SubRouteUrl("user", "User.VerifyRecoveryEmail", new { Code = verifyCode }); - UserHelper.SendRecoveryEmailVerification(_config, user.Username, user.SecuritySettings.RecoveryEmail, resetUrl, verifyUrl); + string verifyUrl = Url.SubRouteUrl("user", "User.VerifyRecoveryEmail", new { Username = user.Username, Code = WebUtility.UrlEncode(token) }); + UserHelper.SendRecoveryEmailVerification(_config, user.Username, userInfo.RecoveryEmail, resetUrl, verifyUrl); return Json(new { result = true }); } return Json(new { error = "The recovery email is already verified" }); @@ -722,7 +830,7 @@ namespace Teknik.Areas.Users.Controllers [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] - public IActionResult SendResetPasswordVerification(string username) + public async Task SendResetPasswordVerification(string username) { if (ModelState.IsValid) { @@ -731,15 +839,16 @@ namespace Teknik.Areas.Users.Controllers User user = UserHelper.GetUser(_dbContext, username); if (user != null) { + IdentityUserInfo userClaims = await IdentityHelper.GetIdentityUserInfo(_config, User.Identity.Name); // If they have a recovery email, let's send a verification - if (!string.IsNullOrEmpty(user.SecuritySettings.RecoveryEmail) && user.SecuritySettings.RecoveryVerified) + if (!string.IsNullOrEmpty(userClaims.RecoveryEmail) && userClaims.RecoveryVerified.HasValue && userClaims.RecoveryVerified.Value) { - string verifyCode = UserHelper.CreateResetPasswordVerification(_dbContext, _config, user); - string resetUrl = Url.SubRouteUrl("user", "User.VerifyResetPassword", new { Username = user.Username, Code = verifyCode }); - UserHelper.SendResetPasswordVerification(_config, user.Username, user.SecuritySettings.RecoveryEmail, resetUrl); + string verifyCode = await IdentityHelper.GeneratePasswordResetToken(_config, User.Identity.Name); + string resetUrl = Url.SubRouteUrl("user", "User.VerifyResetPassword", new { Username = user.Username, Code = WebUtility.UrlEncode(verifyCode) }); + UserHelper.SendResetPasswordVerification(_config, user.Username, userClaims.RecoveryEmail, resetUrl); return Json(new { result = true }); } - return Json(new { error = "The username doesn't have a recovery email specified" }); + return Json(new { error = "The user doesn't have a recovery email specified, or has not been verified." }); } return Json(new { error = "The username is not valid" }); } @@ -753,7 +862,7 @@ namespace Teknik.Areas.Users.Controllers [HttpGet] [AllowAnonymous] - public IActionResult VerifyResetPassword(string username, string code) + public async Task VerifyResetPassword(string username, string code) { bool verified = true; if (string.IsNullOrEmpty(code)) @@ -762,15 +871,12 @@ namespace Teknik.Areas.Users.Controllers // Is there a code? if (verified) { - verified &= UserHelper.VerifyResetPassword(_dbContext, _config, username, code); + // The password reset code is valid, let's get their user account for this session + User user = UserHelper.GetUser(_dbContext, username); + _session.SetString(_AuthSessionKey, user.Username); + _session.SetString("AuthCode", WebUtility.UrlDecode(code)); - if (verified) - { - // The password reset code is valid, let's get their user account for this session - User user = UserHelper.GetUser(_dbContext, username); - HttpContext.Session.Set(_AuthSessionKey, user.Username); - HttpContext.Session.Set("AuthCode", code); - } + await _session.CommitAsync(); } ResetPasswordVerificationViewModel model = new ResetPasswordVerificationViewModel(); @@ -782,16 +888,17 @@ namespace Teknik.Areas.Users.Controllers [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] - public IActionResult SetUserPassword(SetPasswordViewModel passwordViewModel) + public async Task SetUserPassword(SetPasswordViewModel passwordViewModel) { if (ModelState.IsValid) { try { - string code = HttpContext.Session.Get("AuthCode"); + await _session.LoadAsync(); + string code = _session.GetString("AuthCode"); if (!string.IsNullOrEmpty(code)) { - string username = HttpContext.Session.Get(_AuthSessionKey); + string username = _session.GetString(_AuthSessionKey); if (!string.IsNullOrEmpty(username)) { if (string.IsNullOrEmpty(passwordViewModel.Password)) @@ -803,10 +910,19 @@ namespace Teknik.Areas.Users.Controllers return Json(new { error = "Passwords must match" }); } - User newUser = UserHelper.GetUser(_dbContext, username); - UserHelper.EditAccount(_dbContext, _config, newUser, true, passwordViewModel.Password); + try + { + await UserHelper.ResetAccountPassword(_dbContext, _config, username, code, passwordViewModel.Password); - return Json(new { result = true }); + _session.Remove(_AuthSessionKey); + _session.Remove("AuthCode"); + + return Json(new { result = true }); + } + catch (Exception ex) + { + return Json(new { error = ex.Message }); + } } return Json(new { error = "User does not exist" }); } @@ -822,25 +938,100 @@ namespace Teknik.Areas.Users.Controllers [HttpPost] [ValidateAntiForgeryToken] - public IActionResult VerifyAuthenticatorCode(string code) + public async Task Generate2FA() { User user = UserHelper.GetUser(_dbContext, User.Identity.Name); if (user != null) { - if (user.SecuritySettings.TwoFactorEnabled) + // Get User Identity Info + var userInfo = await IdentityHelper.GetIdentityUserInfo(_config, User.Identity.Name); + if (userInfo.TwoFactorEnabled.HasValue && !userInfo.TwoFactorEnabled.Value) { - string key = user.SecuritySettings.TwoFactorKey; + // Validate the code with the identity server + var key = await IdentityHelper.Reset2FAKey(_config, user.Username); - TimeAuthenticator ta = new TimeAuthenticator(usedCodeManager: usedCodesManager); - bool isValid = ta.CheckCode(key, code, user); - - if (isValid) + if (!string.IsNullOrEmpty(key)) { - return Json(new { result = true }); + return Json(new { result = true, key = key, qrUrl = Url.SubRouteUrl("user", "User.Action", new { action = "GenerateAuthQrCode", key = key }) }); + } + return Json(new { error = "Unable to generate Two Factor Authentication key" }); + } + return Json(new { error = "User already has Two Factor Authentication enabled" }); + } + return Json(new { error = "User does not exist" }); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task VerifyAuthenticatorCode(string code) + { + User user = UserHelper.GetUser(_dbContext, User.Identity.Name); + if (user != null) + { + // Get User Identity Info + var userInfo = await IdentityHelper.GetIdentityUserInfo(_config, User.Identity.Name); + if (userInfo.TwoFactorEnabled.HasValue && !userInfo.TwoFactorEnabled.Value) + { + // Validate the code with the identity server + var result = await IdentityHelper.Enable2FA(_config, user.Username, code); + + if (result.Any()) + { + return Json(new { result = true, recoveryCodes = result }); } return Json(new { error = "Invalid Authentication Code" }); } - return Json(new { error = "User does not have Two Factor Authentication enabled" }); + return Json(new { error = "User already has Two Factor Authentication enabled" }); + } + return Json(new { error = "User does not exist" }); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task ResetRecoveryCodes() + { + User user = UserHelper.GetUser(_dbContext, User.Identity.Name); + if (user != null) + { + // Get User Identity Info + var userInfo = await IdentityHelper.GetIdentityUserInfo(_config, User.Identity.Name); + if (userInfo.TwoFactorEnabled.HasValue && userInfo.TwoFactorEnabled.Value) + { + // Regenerate the recovery codes + var result = await IdentityHelper.GenerateRecoveryCodes(_config, user.Username); + + if (result.Any()) + { + return Json(new { result = true, recoveryCodes = result }); + } + return Json(new { error = "Invalid Authentication Code" }); + } + return Json(new { error = "User doesn't have Two Factor Authentication enabled" }); + } + return Json(new { error = "User does not exist" }); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Disable2FA() + { + User user = UserHelper.GetUser(_dbContext, User.Identity.Name); + if (user != null) + { + // Get User Identity Info + var userInfo = await IdentityHelper.GetIdentityUserInfo(_config, User.Identity.Name); + if (userInfo.TwoFactorEnabled.HasValue && userInfo.TwoFactorEnabled.Value) + { + // Validate the code with the identity server + var result = await IdentityHelper.Disable2FA(_config, user.Username); + + if (result.Success) + { + return Json(new { result = true }); + } + return Json(new { error = result.Message }); + } + return Json(new { error = "User doesn't have Two Factor Authentication enabled" }); } return Json(new { error = "User does not exist" }); } @@ -865,23 +1056,23 @@ namespace Teknik.Areas.Users.Controllers User user = UserHelper.GetUser(_dbContext, User.Identity.Name); if (user != null) { - if (user.SecuritySettings.AllowTrustedDevices) - { - // let's clear the trusted devices - user.TrustedDevices.Clear(); - List foundDevices = _dbContext.TrustedDevices.Where(d => d.UserId == user.UserId).ToList(); - if (foundDevices != null) - { - foreach (TrustedDevice device in foundDevices) - { - _dbContext.TrustedDevices.Remove(device); - } - } - _dbContext.Entry(user).State = EntityState.Modified; - _dbContext.SaveChanges(); + //if (user.SecuritySettings.AllowTrustedDevices) + //{ + // // let's clear the trusted devices + // user.TrustedDevices.Clear(); + // List foundDevices = _dbContext.TrustedDevices.Where(d => d.UserId == user.UserId).ToList(); + // if (foundDevices != null) + // { + // foreach (TrustedDevice device in foundDevices) + // { + // _dbContext.TrustedDevices.Remove(device); + // } + // } + // _dbContext.Entry(user).State = EntityState.Modified; + // _dbContext.SaveChanges(); - return Json(new { result = true }); - } + // return Json(new { result = true }); + //} return Json(new { error = "User does not allow trusted devices" }); } return Json(new { error = "User does not exist" }); @@ -901,27 +1092,27 @@ namespace Teknik.Areas.Users.Controllers User user = UserHelper.GetUser(_dbContext, User.Identity.Name); if (user != null) { - string newTokenStr = UserHelper.GenerateAuthToken(_dbContext, user.Username); + //string newTokenStr = UserHelper.GenerateAuthToken(_dbContext, user.Username); - if (!string.IsNullOrEmpty(newTokenStr)) - { - AuthToken token = new AuthToken(); - token.UserId = user.UserId; - token.HashedToken = SHA256.Hash(newTokenStr); - token.Name = name; + //if (!string.IsNullOrEmpty(newTokenStr)) + //{ + // AuthToken token = new AuthToken(); + // token.UserId = user.UserId; + // token.HashedToken = SHA256.Hash(newTokenStr); + // token.Name = name; - _dbContext.AuthTokens.Add(token); - _dbContext.SaveChanges(); + // _dbContext.AuthTokens.Add(token); + // _dbContext.SaveChanges(); - AuthTokenViewModel model = new AuthTokenViewModel(); - model.AuthTokenId = token.AuthTokenId; - model.Name = token.Name; - model.LastDateUsed = token.LastDateUsed; + // 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); + // 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 { result = new { token = newTokenStr, html = renderedView } }); + //} return Json(new { error = "Unable to generate Auth Token" }); } return Json(new { error = "User does not exist" }); @@ -941,15 +1132,15 @@ namespace Teknik.Areas.Users.Controllers User user = UserHelper.GetUser(_dbContext, User.Identity.Name); if (user != null) { - user.AuthTokens.Clear(); - List foundTokens = _dbContext.AuthTokens.Where(d => d.UserId == user.UserId).ToList(); - if (foundTokens != null) - { - foreach (AuthToken token in foundTokens) - { - _dbContext.AuthTokens.Remove(token); - } - } + //user.AuthTokens.Clear(); + //List foundTokens = _dbContext.AuthTokens.Where(d => d.UserId == user.UserId).ToList(); + //if (foundTokens != null) + //{ + // foreach (AuthToken token in foundTokens) + // { + // _dbContext.AuthTokens.Remove(token); + // } + //} _dbContext.Entry(user).State = EntityState.Modified; _dbContext.SaveChanges(); @@ -972,15 +1163,15 @@ namespace Teknik.Areas.Users.Controllers 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(); + //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 { result = new { name = name } }); + //} return Json(new { error = "Authentication Token does not exist" }); } return Json(new { error = "User does not exist" }); @@ -1000,16 +1191,16 @@ namespace Teknik.Areas.Users.Controllers 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(); + //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 { result = true }); + //} return Json(new { error = "Authentication Token does not exist" }); } return Json(new { error = "User does not exist" }); @@ -1045,30 +1236,5 @@ namespace Teknik.Areas.Users.Controllers return Json(new { error = ex.GetFullMessage(true) }); } } - - private async Task SignInUser(User user, string returnUrl, bool rememberMe) - { - var claims = new List - { - new Claim(ClaimTypes.Name, user.Username) - }; - - // Add their roles - foreach (var role in user.UserRoles) - { - claims.Add(new Claim(ClaimTypes.Role, role.Role.Name)); - } - - var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); - - var authProps = new AuthenticationProperties - { - IsPersistent = rememberMe, - ExpiresUtc = DateTime.UtcNow.AddMonths(1), - RedirectUri = returnUrl - }; - - await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProps); - } } } diff --git a/Teknik/Areas/User/Models/AuthToken.cs b/Teknik/Areas/User/Models/AuthToken.cs deleted file mode 100644 index b6e8be6..0000000 --- a/Teknik/Areas/User/Models/AuthToken.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Web; -using Teknik.Attributes; - -namespace Teknik.Areas.Users.Models -{ - public class AuthToken - { - public int AuthTokenId { get; set; } - - public int UserId { get; set; } - - public virtual User User { get; set; } - - public string Name { get; set; } - - [CaseSensitive] - public string HashedToken { get; set; } - - public DateTime? LastDateUsed { get; set; } - } -} \ No newline at end of file diff --git a/Teknik/Areas/User/Models/BlogSettings.cs b/Teknik/Areas/User/Models/BlogSettings.cs index d253539..78800f1 100644 --- a/Teknik/Areas/User/Models/BlogSettings.cs +++ b/Teknik/Areas/User/Models/BlogSettings.cs @@ -10,18 +10,10 @@ namespace Teknik.Areas.Users.Models { public class BlogSettings { - public int UserId { get; set; } - - public virtual User User { get; set; } - - public virtual SecuritySettings SecuritySettings { get; set; } - - public virtual UserSettings UserSettings { get; set; } - - public virtual UploadSettings UploadSettings { get; set; } - + [Column("Title")] public string Title { get; set; } + [Column("Description")] public string Description { get; set; } public BlogSettings() diff --git a/Teknik/Areas/User/Models/IdentityResult.cs b/Teknik/Areas/User/Models/IdentityResult.cs new file mode 100644 index 0000000..af83782 --- /dev/null +++ b/Teknik/Areas/User/Models/IdentityResult.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Identity; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Teknik.Areas.Users.Models +{ + public class IdentityResult + { + public bool Success { get; set; } + public object Data { get; set; } + public string Message { get; set; } + public IEnumerable IdentityErrors { get; set; } + } +} diff --git a/Teknik/Areas/User/Models/IdentityUserInfo.cs b/Teknik/Areas/User/Models/IdentityUserInfo.cs new file mode 100644 index 0000000..66a2456 --- /dev/null +++ b/Teknik/Areas/User/Models/IdentityUserInfo.cs @@ -0,0 +1,127 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Teknik.Utilities; + +namespace Teknik.Areas.Users.Models +{ + public class IdentityUserInfo + { + public DateTime? CreationDate { get; set; } + + public DateTime? LastSeen { get; set; } + + public AccountType? AccountType { get; set; } + + public AccountStatus? AccountStatus { get; set; } + + public string RecoveryEmail { get; set; } + + public bool? RecoveryVerified { get; set; } + + public bool? TwoFactorEnabled { get; set; } + + public string PGPPublicKey { get; set; } + + public IdentityUserInfo() { } + + public IdentityUserInfo(IEnumerable claims) + { + if (claims.FirstOrDefault(c => c.Type == "creation-date") != null) + { + DateTime dateTime = new DateTime(); + if (DateTime.TryParse(claims.FirstOrDefault(c => c.Type == "creation-date").Value, out dateTime)) + CreationDate = dateTime; + } + if (claims.FirstOrDefault(c => c.Type == "last-seen") != null) + { + DateTime dateTime = new DateTime(); + if (DateTime.TryParse(claims.FirstOrDefault(c => c.Type == "last-seen").Value, out dateTime)) + CreationDate = dateTime; + } + if (claims.FirstOrDefault(c => c.Type == "account-type") != null) + { + AccountType accountType = Utilities.AccountType.Basic; + if (Enum.TryParse(claims.FirstOrDefault(c => c.Type == "account-type").Value, out accountType)) + AccountType = accountType; + } + if (claims.FirstOrDefault(c => c.Type == "account-status") != null) + { + AccountStatus accountStatus = Utilities.AccountStatus.Active; + if (Enum.TryParse(claims.FirstOrDefault(c => c.Type == "account-status").Value, out accountStatus)) + AccountStatus = accountStatus; + } + if (claims.FirstOrDefault(c => c.Type == "recovery-email") != null) + { + RecoveryEmail = claims.FirstOrDefault(c => c.Type == "recovery-email").Value; + } + if (claims.FirstOrDefault(c => c.Type == "recovery-verified") != null) + { + bool verified = false; + if (bool.TryParse(claims.FirstOrDefault(c => c.Type == "recovery-verified").Value, out verified)) + RecoveryVerified = verified; + } + if (claims.FirstOrDefault(c => c.Type == "2fa-enabled") != null) + { + bool twoFactor = false; + if (bool.TryParse(claims.FirstOrDefault(c => c.Type == "2fa-enabled").Value, out twoFactor)) + TwoFactorEnabled = twoFactor; + } + if (claims.FirstOrDefault(c => c.Type == "pgp-public-key") != null) + { + PGPPublicKey = claims.FirstOrDefault(c => c.Type == "pgp-public-key").Value; + } + } + + public IdentityUserInfo(JObject info) + { + if (info["creation-date"] != null) + { + DateTime dateTime = new DateTime(); + if (DateTime.TryParse(info["creation-date"].ToString(), out dateTime)) + CreationDate = dateTime; + } + if (info["last-seen"] != null) + { + DateTime dateTime = new DateTime(); + if (DateTime.TryParse(info["last-seen"].ToString(), out dateTime)) + LastSeen = dateTime; + } + if (info["account-type"] != null) + { + AccountType accountType = Utilities.AccountType.Basic; + if (Enum.TryParse(info["account-type"].ToString(), out accountType)) + AccountType = accountType; + } + if (info["account-status"] != null) + { + AccountStatus accountStatus = Utilities.AccountStatus.Active; + if (Enum.TryParse(info["account-status"].ToString(), out accountStatus)) + AccountStatus = accountStatus; + } + if (info["recovery-email"] != null) + { + RecoveryEmail = info["recovery-email"].ToString(); + } + if (info["recovery-verified"] != null) + { + bool verified = false; + if (bool.TryParse(info["recovery-verified"].ToString(), out verified)) + RecoveryVerified = verified; + } + if (info["2fa-enabled"] != null) + { + bool twoFactor = false; + if (bool.TryParse(info["2fa-enabled"].ToString(), out twoFactor)) + TwoFactorEnabled = twoFactor; + } + if (info["pgp-public-key"] != null) + { + PGPPublicKey = info["pgp-public-key"].ToString(); + } + } + } +} diff --git a/Teknik/Areas/User/Models/RecoveryEmailVerification.cs b/Teknik/Areas/User/Models/RecoveryEmailVerification.cs deleted file mode 100644 index 03b7380..0000000 --- a/Teknik/Areas/User/Models/RecoveryEmailVerification.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using Teknik.Attributes; - -namespace Teknik.Areas.Users.Models -{ - public class RecoveryEmailVerification - { - public int RecoveryEmailVerificationId { get; set; } - - public int UserId { get; set; } - - public virtual User User { get; set; } - - [CaseSensitive] - public string Code { get; set; } - - public DateTime DateCreated { get; set; } - } -} \ No newline at end of file diff --git a/Teknik/Areas/User/Models/ResetPasswordVerification.cs b/Teknik/Areas/User/Models/ResetPasswordVerification.cs deleted file mode 100644 index b171f3d..0000000 --- a/Teknik/Areas/User/Models/ResetPasswordVerification.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using Teknik.Attributes; - -namespace Teknik.Areas.Users.Models -{ - public class ResetPasswordVerification - { - public int ResetPasswordVerificationId { get; set; } - - public int UserId { get; set; } - - public virtual User User { get; set; } - - [CaseSensitive] - public string Code { get; set; } - - public DateTime DateCreated { get; set; } - } -} \ No newline at end of file diff --git a/Teknik/Areas/User/Models/Role.cs b/Teknik/Areas/User/Models/Role.cs deleted file mode 100644 index 725e49e..0000000 --- a/Teknik/Areas/User/Models/Role.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Teknik.Areas.Users.Models -{ - public class Role - { - public int RoleId { get; set; } - - public string Name { get; set; } - - public string Description { get; set; } - - public virtual ICollection UserRoles { get; set; } - } -} diff --git a/Teknik/Areas/User/Models/SecuritySettings.cs b/Teknik/Areas/User/Models/SecuritySettings.cs deleted file mode 100644 index 12b298d..0000000 --- a/Teknik/Areas/User/Models/SecuritySettings.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Web; -using Teknik.Attributes; - -namespace Teknik.Areas.Users.Models -{ - public class SecuritySettings - { - public int UserId { get; set; } - - public virtual User User { get; set; } - - public virtual UserSettings UserSettings { get; set; } - - public virtual BlogSettings BlogSettings { get; set; } - - public virtual UploadSettings UploadSettings { get; set; } - - public string RecoveryEmail { get; set; } - - public bool RecoveryVerified { get; set; } - - public bool AllowTrustedDevices { get; set; } - - public bool TwoFactorEnabled { get; set; } - - [CaseSensitive] - public string TwoFactorKey { get; set; } - - public string PGPSignature { get; set; } - - public SecuritySettings() - { - RecoveryEmail = string.Empty; - RecoveryVerified = false; - AllowTrustedDevices = false; - TwoFactorEnabled = false; - TwoFactorKey = string.Empty; - PGPSignature = string.Empty; - } - } -} \ No newline at end of file diff --git a/Teknik/Areas/User/Models/TrustedDevice.cs b/Teknik/Areas/User/Models/TrustedDevice.cs deleted file mode 100644 index 250f65d..0000000 --- a/Teknik/Areas/User/Models/TrustedDevice.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using Teknik.Attributes; - -namespace Teknik.Areas.Users.Models -{ - public class TrustedDevice - { - public int TrustedDeviceId { get; set; } - - public int UserId { get; set; } - - public virtual User User { get; set; } - - public string Name { get; set; } - - [CaseSensitive] - public string Token { get; set; } - - public DateTime DateSeen { get; set; } - } -} \ No newline at end of file diff --git a/Teknik/Areas/User/Models/UploadSettings.cs b/Teknik/Areas/User/Models/UploadSettings.cs index 3643c90..95a2239 100644 --- a/Teknik/Areas/User/Models/UploadSettings.cs +++ b/Teknik/Areas/User/Models/UploadSettings.cs @@ -10,16 +10,7 @@ namespace Teknik.Areas.Users.Models { public class UploadSettings { - public int UserId { get; set; } - - public virtual User User { get; set; } - - public virtual SecuritySettings SecuritySettings { get; set; } - - public virtual BlogSettings BlogSettings { get; set; } - - public virtual UserSettings UserSettings { get; set; } - + [Column("Encrypt")] public bool Encrypt { get; set; } public UploadSettings() diff --git a/Teknik/Areas/User/Models/User.cs b/Teknik/Areas/User/Models/User.cs index db77d2c..397c2f5 100644 --- a/Teknik/Areas/User/Models/User.cs +++ b/Teknik/Areas/User/Models/User.cs @@ -15,42 +15,18 @@ namespace Teknik.Areas.Users.Models public string Username { get; set; } - [NotMapped] - public string Password { get; set; } - - [CaseSensitive] - public string HashedPassword { get; set; } - public virtual ICollection Logins { get; set; } - public virtual ICollection Transfers { get; set; } - - public DateTime JoinDate { get; set; } - - public DateTime LastSeen { get; set; } - public virtual InviteCode ClaimedInviteCode { get; set; } public virtual ICollection OwnedInviteCodes { get; set; } - - public AccountType AccountType { get; set; } - - public AccountStatus AccountStatus { get; set; } - - public virtual ICollection UserRoles { get; set; } public virtual UserSettings UserSettings { get; set; } - public virtual SecuritySettings SecuritySettings { get; set; } - public virtual BlogSettings BlogSettings { get; set; } public virtual UploadSettings UploadSettings { get; set; } - public virtual ICollection TrustedDevices { get; set; } - - public virtual ICollection AuthTokens { get; set; } - public virtual ICollection Uploads { get; set; } public virtual ICollection Pastes { get; set; } @@ -62,17 +38,7 @@ namespace Teknik.Areas.Users.Models public User() { Username = string.Empty; - Password = string.Empty; - HashedPassword = string.Empty; Logins = new List(); - Transfers = new List(); - JoinDate = DateTime.Now; - LastSeen = DateTime.Now; - AccountType = AccountType.Basic; - AccountStatus = AccountStatus.Active; - UserRoles = new List(); - TrustedDevices = new List(); - AuthTokens = new List(); ClaimedInviteCode = null; OwnedInviteCodes = new List(); } diff --git a/Teknik/Areas/User/Models/UserRole.cs b/Teknik/Areas/User/Models/UserRole.cs deleted file mode 100644 index 03c34b8..0000000 --- a/Teknik/Areas/User/Models/UserRole.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Teknik.Areas.Users.Models -{ - public class UserRole - { - public int UserRoleId { get; set; } - - public int UserId { get; set; } - - public virtual User User { get; set; } - - public int RoleId { get; set; } - - public virtual Role Role { get; set; } - } -} diff --git a/Teknik/Areas/User/Models/UserSettings.cs b/Teknik/Areas/User/Models/UserSettings.cs index 978c182..feb8b64 100644 --- a/Teknik/Areas/User/Models/UserSettings.cs +++ b/Teknik/Areas/User/Models/UserSettings.cs @@ -10,20 +10,13 @@ namespace Teknik.Areas.Users.Models { public class UserSettings { - public int UserId { get; set; } - - public virtual User User { get; set; } - - public virtual SecuritySettings SecuritySettings { get; set; } - - public virtual BlogSettings BlogSettings { get; set; } - - public virtual UploadSettings UploadSettings { get; set; } - + [Column("About")] public string About { get; set; } + [Column("Website")] public string Website { get; set; } + [Column("Quote")] public string Quote { get; set; } public UserSettings() diff --git a/Teknik/Areas/User/Utility/IdentityHelper.cs b/Teknik/Areas/User/Utility/IdentityHelper.cs new file mode 100644 index 0000000..47c26c1 --- /dev/null +++ b/Teknik/Areas/User/Utility/IdentityHelper.cs @@ -0,0 +1,328 @@ +using IdentityModel.Client; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Teknik.Areas.Users.Models; +using Teknik.Configuration; +using Teknik.Utilities; + +namespace Teknik.Areas.Users.Utility +{ + public static class IdentityHelper + { + public static async Task GetAccessToken(Config config) + { + return await GetAccessToken(config.UserConfig.IdentityServerConfig.Authority, config.UserConfig.IdentityServerConfig.ClientId, config.UserConfig.IdentityServerConfig.ClientSecret, "auth-api"); + } + + public static async Task GetAccessToken(string authority, string clientId, string secret, string scope) + { + var disco = await DiscoveryClient.GetAsync(authority); + if (disco.IsError) throw new Exception(disco.Error); + + var tokenClient = new TokenClient(disco.TokenEndpoint, clientId, secret); + var tokenResponse = await tokenClient.RequestClientCredentialsAsync(scope); + + if (tokenResponse.IsError) throw new Exception(tokenResponse.Error); + + return tokenResponse.AccessToken; + } + + public static Uri CreateUrl(Config config, string path) + { + var authUrl = new Uri(config.UserConfig.IdentityServerConfig.Authority); + return new Uri(authUrl, path); + } + + public static async Task Get(Config config, Uri url) + { + var client = new HttpClient(); + client.SetBearerToken(await GetAccessToken(config)); + + var content = await client.GetStringAsync(url); + if (!string.IsNullOrEmpty(content)) + { + return JsonConvert.DeserializeObject(content); + } + + return new IdentityResult() { Success = false, Message = "No Data Received" }; + } + + public static async Task Post(Config config, Uri url, object data) + { + var client = new HttpClient(); + client.SetBearerToken(await GetAccessToken(config)); + + var response = await client.PostAsJsonAsync(url, data); + if (response.IsSuccessStatusCode) + { + string content = await response.Content.ReadAsStringAsync(); + if (!string.IsNullOrEmpty(content)) + { + return JsonConvert.DeserializeObject(content); + } + return new IdentityResult() { Success = false, Message = "No Data Received" }; + } + return new IdentityResult() { Success = false, Message = "HTTP Error: " + response.StatusCode + " | " + (await response.Content.ReadAsStringAsync()) }; + } + + // API Functions + + public static async Task CreateUser(Config config, string username, string password, string recoveryEmail) + { + var manageUrl = CreateUrl(config, $"Manage/CreateUser"); + + var response = await Post(config, manageUrl, + new + { + username = username, + password = password, + recoveryEmail = recoveryEmail + }); + return response; + } + + public static async Task DeleteUser(Config config, string username) + { + var manageUrl = CreateUrl(config, $"Manage/DeleteUser"); + + var response = await Post(config, manageUrl, + new + { + username = username + }); + return response.Success; + } + + public static async Task UserExists(Config config, string username) + { + var manageUrl = CreateUrl(config, $"Manage/UserExists?username={username}"); + + var result = await Get(config, manageUrl); + if (result.Success) + { + return (bool)result.Data; + } + throw new Exception(result.Message); + } + + public static async Task GetIdentityUserInfo(Config config, string username) + { + var manageUrl = CreateUrl(config, $"Manage/GetUserInfo?username={username}"); + + var result = await Get(config, manageUrl); + if (result.Success) + { + return new IdentityUserInfo((JObject)result.Data); + } + throw new Exception(result.Message); + } + + public static async Task CheckPassword(Config config, string username, string password) + { + var manageUrl = CreateUrl(config, $"Manage/CheckPassword"); + + var response = await Post(config, manageUrl, + new + { + username = username, + password = password + }); + if (response.Success) + { + return (bool)response.Data; + } + return false; + } + + public static async Task GeneratePasswordResetToken(Config config, string username) + { + var manageUrl = CreateUrl(config, $"Manage/GeneratePasswordResetToken"); + + var response = await Post(config, manageUrl, + new + { + username = username + }); + if (response.Success) + { + return (string)response.Data; + } + throw new Exception(response.Message); + } + + public static async Task ResetPassword(Config config, string username, string token, string newPassword) + { + var manageUrl = CreateUrl(config, $"Manage/ResetPassword"); + + var response = await Post(config, manageUrl, + new + { + username = username, + token = token, + password = newPassword + }); + return response; + } + + public static async Task UpdatePassword(Config config, string username, string currentPassword, string newPassword) + { + var manageUrl = CreateUrl(config, $"Manage/UpdatePassword"); + + var response = await Post(config, manageUrl, + new + { + username = username, + currentPassword = currentPassword, + newPassword = newPassword + }); + return response; + } + + public static async Task UpdateRecoveryEmail(Config config, string username, string email) + { + var manageUrl = CreateUrl(config, $"Manage/UpdateEmail"); + + var response = await Post(config, manageUrl, + new + { + username = username, + email = email + }); + if (response.Success) + { + return (string)response.Data; + } + throw new Exception(response.Message); + } + + public static async Task VerifyRecoveryEmail(Config config, string username, string token) + { + var manageUrl = CreateUrl(config, $"Manage/VerifyEmail"); + + var response = await Post(config, manageUrl, + new + { + username = username, + token = token + }); + return response; + } + + public static async Task UpdateAccountStatus(Config config, string username, AccountStatus accountStatus) + { + var manageUrl = CreateUrl(config, $"Manage/UpdateAccountStatus"); + + var response = await Post(config, manageUrl, + new + { + username = username, + accountStatus = accountStatus + }); + return response; + } + + public static async Task UpdateAccountType(Config config, string username, AccountType accountType) + { + var manageUrl = CreateUrl(config, $"Manage/UpdateAccountType"); + + var response = await Post(config, manageUrl, + new + { + username = username, + accountType = accountType + }); + return response; + } + + public static async Task UpdatePGPPublicKey(Config config, string username, string publicKey) + { + var manageUrl = CreateUrl(config, $"Manage/UpdateAccountType"); + + var response = await Post(config, manageUrl, + new + { + username = username, + pgpPublicKey = publicKey + }); + return response; + } + + public static async Task Get2FAKey(Config config, string username) + { + var manageUrl = CreateUrl(config, $"Manage/Get2FAKey?username={username}"); + + var result = await Get(config, manageUrl); + if (result.Success) + { + return (string)result.Data; + } + throw new Exception(result.Message); + } + + public static async Task Reset2FAKey(Config config, string username) + { + var manageUrl = CreateUrl(config, $"Manage/Reset2FAKey"); + + var response = await Post(config, manageUrl, + new + { + username = username + }); + if (response.Success) + { + return (string)response.Data; + } + throw new Exception(response.Message); + } + + public static async Task Enable2FA(Config config, string username, string code) + { + var manageUrl = CreateUrl(config, $"Manage/Enable2FA"); + + var response = await Post(config, manageUrl, + new + { + username = username, + code = code + }); + if (response.Success) + { + return ((JArray)response.Data).ToObject(); + } + throw new Exception(response.Message); + } + + public static async Task Disable2FA(Config config, string username) + { + var manageUrl = CreateUrl(config, $"Manage/Disable2FA"); + + var response = await Post(config, manageUrl, + new + { + username = username + }); + return response; + } + + public static async Task GenerateRecoveryCodes(Config config, string username) + { + var manageUrl = CreateUrl(config, $"Manage/GenerateRecoveryCodes"); + + var response = await Post(config, manageUrl, + new + { + username = username + }); + if (response.Success) + { + return ((JArray)response.Data).ToObject(); + } + throw new Exception(response.Message); + } + } +} diff --git a/Teknik/Areas/User/Utility/UserHelper.cs b/Teknik/Areas/User/Utility/UserHelper.cs index 9755678..077e3f8 100644 --- a/Teknik/Areas/User/Utility/UserHelper.cs +++ b/Teknik/Areas/User/Utility/UserHelper.cs @@ -27,6 +27,11 @@ using System.Security.Claims; using Microsoft.AspNetCore.Authentication.Cookies; using Teknik.MailService; using Teknik.GitService; +using IdentityModel.Client; +using System.Net.Http; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; +using Microsoft.AspNetCore.Mvc; namespace Teknik.Areas.Users.Utility { @@ -75,12 +80,13 @@ namespace Teknik.Areas.Users.Utility return isValid; } - public static bool UsernameAvailable(TeknikEntities db, Config config, string username) + public static async Task UsernameAvailable(TeknikEntities db, Config config, string username) { bool isAvailable = true; isAvailable &= ValidUsername(config, username); isAvailable &= !UsernameReserved(config, username); + isAvailable &= !await IdentityHelper.UserExists(config, username); isAvailable &= !UserExists(db, username); isAvailable &= !UserEmailExists(config, GetUserEmailAddress(config, username)); isAvailable &= !UserGitExists(config, username); @@ -88,29 +94,35 @@ namespace Teknik.Areas.Users.Utility return isAvailable; } - public static DateTime GetLastAccountActivity(TeknikEntities db, Config config, User user) + public static async Task GetLastAccountActivity(TeknikEntities db, Config config, string username) + { + var userInfo = await IdentityHelper.GetIdentityUserInfo(config, username); + return GetLastAccountActivity(db, config, username, userInfo); + } + + public static DateTime GetLastAccountActivity(TeknikEntities db, Config config, string username, IdentityUserInfo userInfo) { try { DateTime lastActive = new DateTime(1900, 1, 1); - if (UserEmailExists(config, GetUserEmailAddress(config, user.Username))) + if (UserEmailExists(config, GetUserEmailAddress(config, username))) { - DateTime emailLastActive = UserEmailLastActive(config, GetUserEmailAddress(config, user.Username)); + DateTime emailLastActive = UserEmailLastActive(config, GetUserEmailAddress(config, username)); if (lastActive < emailLastActive) lastActive = emailLastActive; } - if (UserGitExists(config, user.Username)) + if (UserGitExists(config, username)) { - DateTime gitLastActive = UserGitLastActive(config, user.Username); + DateTime gitLastActive = UserGitLastActive(config, username); if (lastActive < gitLastActive) lastActive = gitLastActive; } - if (UserExists(db, user.Username)) + if (userInfo.LastSeen.HasValue) { - DateTime userLastActive = UserLastActive(db, config, user); + DateTime userLastActive = userInfo.LastSeen.Value; if (lastActive < userLastActive) lastActive = userLastActive; } @@ -123,76 +135,33 @@ namespace Teknik.Areas.Users.Utility } } - public static string GeneratePassword(Config config, User user, string password) + public static async Task CreateAccount(TeknikEntities db, Config config, IUrlHelper url, string username, string password, string recoveryEmail, string inviteCode) { try { - string username = user.Username.ToLower(); - if (user.Transfers.ToList().Exists(t => t.Type == TransferTypes.CaseSensitivePassword)) + var result = await IdentityHelper.CreateUser(config, username, password, recoveryEmail); + if (result.Success) { - username = user.Username; - } - byte[] hashBytes = SHA384.Hash(username, password); - string hash = hashBytes.ToHex(); + // Create an Email Account + CreateUserEmail(config, GetUserEmailAddress(config, username), password); - if (user.Transfers.ToList().Exists(t => t.Type == TransferTypes.ASCIIPassword)) - { - hash = Encoding.ASCII.GetString(hashBytes); - } + // Create a Git Account + CreateUserGit(config, username, password); - if (user.Transfers.ToList().Exists(t => t.Type == TransferTypes.Sha256Password)) - { - hash = SHA256.Hash(password, config.Salt1, config.Salt2); - } + // Add User + User newUser = CreateUser(db, config, username, inviteCode); - return hash; - } - catch (Exception ex) - { - throw new Exception("Unable to generate password.", ex); - } - } - - public static string GenerateAuthToken(TeknikEntities db, string username) - { - try - { - bool validToken = false; - string token = string.Empty; - while (!validToken) - { - username = username.ToLower(); - byte[] hashBytes = SHA384.Hash(username, StringHelper.RandomString(24)); - token = hashBytes.ToHex(); - - // Make sure it isn't a duplicate - string hashedToken = SHA256.Hash(token); - if (!db.AuthTokens.Where(t => t.HashedToken == hashedToken).Any()) + // If they have a recovery email, let's send a verification + if (!string.IsNullOrEmpty(recoveryEmail)) { - validToken = true; + var token = await IdentityHelper.UpdateRecoveryEmail(config, username, recoveryEmail); + string resetUrl = url.SubRouteUrl("user", "User.ResetPassword", new { Username = username }); + string verifyUrl = url.SubRouteUrl("user", "User.VerifyRecoveryEmail", new { Code = WebUtility.UrlEncode(token) }); + SendRecoveryEmailVerification(config, username, recoveryEmail, resetUrl, verifyUrl); } + return; } - - return token; - } - catch (Exception ex) - { - throw new Exception("Unable to generate user auth token.", ex); - } - } - - public static void AddAccount(TeknikEntities db, Config config, User user, string password) - { - try - { - // Create an Email Account - AddUserEmail(config, GetUserEmailAddress(config, user.Username), password); - - // Create a Git Account - AddUserGit(config, user.Username, password); - - // Add User - AddUser(db, config, user, password); + throw new Exception("Error creating account: " + result.Message); } catch (Exception ex) { @@ -201,37 +170,11 @@ namespace Teknik.Areas.Users.Utility } public static void EditAccount(TeknikEntities db, Config config, User user) - { - EditAccount(db, config, user, false, string.Empty); - } - - public static void EditAccount(TeknikEntities db, Config config, User user, bool changePass, string password) { try { - // Changing Password? - if (changePass) - { - // Make sure they have a git and email account before resetting their password - string email = GetUserEmailAddress(config, user.Username); - if (config.EmailConfig.Enabled && !UserEmailExists(config, email)) - { - AddUserEmail(config, email, password); - } - - if (config.GitConfig.Enabled && !UserGitExists(config, user.Username)) - { - AddUserGit(config, user.Username, password); - } - - // Change email password - EditUserEmailPassword(config, GetUserEmailAddress(config, user.Username), password); - - // Update Git password - EditUserGitPassword(config, user.Username, password); - } // Update User - EditUser(db, config, user, changePass, password); + EditUser(db, config, user); } catch (Exception ex) { @@ -239,39 +182,95 @@ namespace Teknik.Areas.Users.Utility } } - public static void EditAccountType(TeknikEntities db, Config config, string username, AccountType type) + public static async Task ChangeAccountPassword(TeknikEntities db, Config config, string username, string currentPassword, string newPassword) + { + IdentityResult result = await IdentityHelper.UpdatePassword(config, username, currentPassword, newPassword); + if (result.Success) + { + ChangeServicePasswords(db, config, username, newPassword); + } + else + { + throw new Exception(result.Message); + } + } + + public static async Task ResetAccountPassword(TeknikEntities db, Config config, string username, string token, string newPassword) + { + IdentityResult result = await IdentityHelper.ResetPassword(config, username, token, newPassword); + if (result.Success) + { + ChangeServicePasswords(db, config, username, newPassword); + } + else + { + throw new Exception(result.Message); + } + } + + public static void ChangeServicePasswords(TeknikEntities db, Config config, string username, string newPassword) + { + try + { + // Make sure they have a git and email account before resetting their password + string email = GetUserEmailAddress(config, username); + if (config.EmailConfig.Enabled && !UserEmailExists(config, email)) + { + CreateUserEmail(config, email, newPassword); + } + + if (config.GitConfig.Enabled && !UserGitExists(config, username)) + { + CreateUserGit(config, username, newPassword); + } + + // Change email password + EditUserEmailPassword(config, GetUserEmailAddress(config, username), newPassword); + + // Update Git password + EditUserGitPassword(config, username, newPassword); + } + catch (Exception ex) + { + throw new Exception("Unable to change service password.", ex); + } + } + + public static async Task EditAccountType(TeknikEntities db, Config config, string username, AccountType type) { try { if (!UserExists(db, username)) throw new Exception($"The user provided does not exist: {username}"); - // Get the user to edit - User user = GetUser(db, username); + var result = await IdentityHelper.UpdateAccountType(config, username, type); - string email = GetUserEmailAddress(config, username); - - // Edit the user type - user.AccountType = type; - EditUser(db, config, user); - - // Add/Remove account type features depending on the type - switch (type) + if (result.Success) { - case AccountType.Basic: - // Set the email size to 1GB - EditUserEmailMaxSize(config, email, config.EmailConfig.MaxSize); - // Set the email max/day to 100 - EditUserEmailMaxEmailsPerDay(config, email, 100); - break; - case AccountType.Premium: - // Set the email size to 5GB - EditUserEmailMaxSize(config, email, 5000); + string email = GetUserEmailAddress(config, username); + // Add/Remove account type features depending on the type + switch (type) + { + case AccountType.Basic: + // Set the email size to 1GB + EditUserEmailMaxSize(config, email, config.EmailConfig.MaxSize); - // Set the email max/day to infinite (-1) - EditUserEmailMaxEmailsPerDay(config, email, -1); - break; + // Set the email max/day to 100 + EditUserEmailMaxEmailsPerDay(config, email, 100); + break; + case AccountType.Premium: + // Set the email size to 5GB + EditUserEmailMaxSize(config, email, 5000); + + // Set the email max/day to infinite (-1) + EditUserEmailMaxEmailsPerDay(config, email, -1); + break; + } + } + else + { + throw new Exception($"Unable to edit the account type [{type}] for {username}: " + result.Message); } } catch (Exception ex) @@ -280,37 +279,39 @@ namespace Teknik.Areas.Users.Utility } } - public static void EditAccountStatus(TeknikEntities db, Config config, string username, AccountStatus status) + public static async Task EditAccountStatus(TeknikEntities db, Config config, string username, AccountStatus status) { try { if (!UserExists(db, username)) throw new Exception($"The user provided does not exist: {username}"); - // Get the user to edit - User user = GetUser(db, username); + var result = await IdentityHelper.UpdateAccountStatus(config, username, status); - string email = GetUserEmailAddress(config, username); - - // Edit the user type - user.AccountStatus = status; - EditUser(db, config, user); - - // Add/Remove account type features depending on the type - switch (status) + if (result.Success) { - case AccountStatus.Active: - // Enable Email - EnableUserEmail(config, email); - // Enable Git - EnableUserGit(config, username); - break; - case AccountStatus.Banned: - // Disable Email - DisableUserEmail(config, email); - // Disable Git - DisableUserGit(config, username); - break; + string email = GetUserEmailAddress(config, username); + + // Add/Remove account type features depending on the type + switch (status) + { + case AccountStatus.Active: + // Enable Email + EnableUserEmail(config, email); + // Enable Git + EnableUserGit(config, username); + break; + case AccountStatus.Banned: + // Disable Email + DisableUserEmail(config, email); + // Disable Git + DisableUserGit(config, username); + break; + } + } + else + { + throw new Exception($"Unable to edit the account status [{status}] for {username}: " + result.Message); } } catch (Exception ex) @@ -319,20 +320,32 @@ namespace Teknik.Areas.Users.Utility } } - public static void DeleteAccount(TeknikEntities db, Config config, User user) + public static async Task DeleteAccount(TeknikEntities db, Config config, User user) { try { - // Delete Email Account - if (UserEmailExists(config, GetUserEmailAddress(config, user.Username))) - DeleteUserEmail(config, GetUserEmailAddress(config, user.Username)); + string username = user.Username; - // Delete Git Account - if (UserGitExists(config, user.Username)) - DeleteUserGit(config, user.Username); + // Delete identity account + var result = await IdentityHelper.DeleteUser(config, username); - // Delete User Account - DeleteUser(db, config, user); + if (result) + { + // Delete User Account + DeleteUser(db, config, user); + + // Delete Email Account + if (UserEmailExists(config, GetUserEmailAddress(config, username))) + DeleteUserEmail(config, GetUserEmailAddress(config, username)); + + // Delete Git Account + if (UserGitExists(config, username)) + DeleteUserGit(config, username); + } + else + { + throw new Exception("Unable to delete identity account."); + } } catch (Exception ex) { @@ -346,7 +359,6 @@ namespace Teknik.Areas.Users.Utility { User user = db.Users .Include(u => u.UserSettings) - .Include(u => u.SecuritySettings) .Include(u => u.BlogSettings) .Include(u => u.UploadSettings) .Where(b => b.Username == username).FirstOrDefault(); @@ -354,16 +366,6 @@ namespace Teknik.Areas.Users.Utility return user; } - public static User GetUserFromToken(TeknikEntities db, string username, string token) - { - if (token != null && !string.IsNullOrEmpty(username)) - { - string hashedToken = SHA256.Hash(token); - return db.Users.FirstOrDefault(u => u.AuthTokens.Select(a => a.HashedToken).Contains(hashedToken) && u.Username == username); - } - return null; - } - public static bool UserExists(TeknikEntities db, string username) { User user = GetUser(db, username); @@ -375,55 +377,11 @@ namespace Teknik.Areas.Users.Utility return false; } - public static DateTime UserLastActive(TeknikEntities db, Config config, User user) + public static async Task UserPasswordCorrect(Config config, string username, string password) { try { - DateTime lastActive = new DateTime(1900, 1, 1); - - if (lastActive < user.LastSeen) - lastActive = user.LastSeen; - - return lastActive; - } - catch (Exception ex) - { - throw new Exception("Unable to determine last user activity.", ex); - } - } - - public static void UpdateTokenLastUsed(TeknikEntities db, string username, string token, DateTime lastUsed) - { - User foundUser = GetUser(db, username); - if (foundUser != null) - { - // Update the user's last seen date - if (foundUser.LastSeen < lastUsed) - { - foundUser.LastSeen = lastUsed; - db.Entry(foundUser).State = EntityState.Modified; - } - - string hashedToken = SHA256.Hash(token); - List tokens = foundUser.AuthTokens.Where(t => t.HashedToken == hashedToken).ToList(); - if (tokens != null) - { - foreach (AuthToken foundToken in tokens) - { - foundToken.LastDateUsed = lastUsed; - db.Entry(foundToken).State = EntityState.Modified; - } - } - db.SaveChanges(); - } - } - - public static bool UserPasswordCorrect(TeknikEntities db, Config config, User user, string password) - { - try - { - string hash = GeneratePassword(config, user, password); - return db.Users.Any(b => b.Username == user.Username && b.HashedPassword == hash); + return await IdentityHelper.CheckPassword(config, username, password); } catch (Exception ex) { @@ -431,102 +389,37 @@ namespace Teknik.Areas.Users.Utility } } - public static bool UserTokenCorrect(TeknikEntities db, string username, string token) - { - User foundUser = GetUserFromToken(db, username, token); - if (foundUser != null) - { - return true; - } - return false; - } - - public static bool UserHasRoles(User user, params string[] roles) - { - bool hasRole = true; - if (user != null) - { - // Check if they have the role specified - if (roles.Any()) - { - foreach (string role in roles) - { - if (!string.IsNullOrEmpty(role)) - { - if (user.UserRoles.Where(ur => ur.Role.Name == role).Any()) - { - // They have the role! - return true; - } - else - { - // They don't have this role, so let's reset the hasRole - hasRole = false; - } - } - else - { - // Only set this if we haven't failed once already - hasRole &= true; - } - } - } - else - { - // No roles to check, so they pass! - return true; - } - } - else - { - hasRole = false; - } - return hasRole; - } - - public static void TransferUser(TeknikEntities db, Config config, User user, string password) + public static User CreateUser(TeknikEntities db, Config config, string username, string inviteCode) { try { - List transfers = user.Transfers.ToList(); - for (int i = 0; i < transfers.Count; i++) - { - TransferType transfer = transfers[i]; - switch (transfer.Type) - { - case TransferTypes.Sha256Password: - case TransferTypes.CaseSensitivePassword: - case TransferTypes.ASCIIPassword: - user.HashedPassword = SHA384.Hash(user.Username.ToLower(), password).ToHex(); - break; - default: - break; - } - user.Transfers.Remove(transfer); - } - db.Entry(user).State = EntityState.Modified; - db.SaveChanges(); - } - catch (Exception ex) - { - throw new Exception("Unable to transfer user info.", ex); - } - } + User newUser = new User(); + newUser.Username = username; + newUser.UserSettings = new UserSettings(); + newUser.BlogSettings = new BlogSettings(); + newUser.UploadSettings = new UploadSettings(); + + // if they provided an invite code, let's assign them to it + if (!string.IsNullOrEmpty(inviteCode)) + { + InviteCode code = db.InviteCodes.Where(c => c.Code == inviteCode).FirstOrDefault(); + db.Entry(code).State = EntityState.Modified; + + newUser.ClaimedInviteCode = code; + } - public static void AddUser(TeknikEntities db, Config config, User user, string password) - { - try - { // Add User - user.HashedPassword = GeneratePassword(config, user, password); - db.Users.Add(user); - db.SaveChanges(); + db.Users.Add(newUser); // Generate blog for the user var newBlog = new Blog.Models.Blog(); - newBlog.UserId = user.UserId; + newBlog.User = newUser; db.Blogs.Add(newBlog); + + // Save the changes db.SaveChanges(); + + return newUser; } catch (Exception ex) { @@ -535,31 +428,9 @@ namespace Teknik.Areas.Users.Utility } public static void EditUser(TeknikEntities db, Config config, User user) - { - EditUser(db, config, user, false, string.Empty); - } - - public static void EditUser(TeknikEntities db, Config config, User user, bool changePass, string password) { try { - // Changing Password? - if (changePass) - { - // Update User password - user.HashedPassword = SHA384.Hash(user.Username.ToLower(), password).ToHex(); - - // Remove any password transfer items for the account - for (int i = 0; i < user.Transfers.Count; i++) - { - TransferType type = user.Transfers.ToList()[i]; - if (type.Type == TransferTypes.ASCIIPassword || type.Type == TransferTypes.CaseSensitivePassword || type.Type == TransferTypes.Sha256Password) - { - user.Transfers.Remove(type); - i--; - } - } - } db.Entry(user).State = EntityState.Modified; db.SaveChanges(); } @@ -651,28 +522,6 @@ namespace Teknik.Areas.Users.Utility db.SaveChanges(); } - // Delete Recovery Email Verifications - List verCodes = db.RecoveryEmailVerifications.Where(r => r.User.Username == user.Username).ToList(); - if (verCodes.Any()) - { - foreach (RecoveryEmailVerification verCode in verCodes) - { - db.RecoveryEmailVerifications.Remove(verCode); - } - db.SaveChanges(); - } - - // Delete Password Reset Verifications - List verPass = db.ResetPasswordVerifications.Where(r => r.User.Username == user.Username).ToList(); - if (verPass.Any()) - { - foreach (ResetPasswordVerification ver in verPass) - { - db.ResetPasswordVerifications.Remove(ver); - } - db.SaveChanges(); - } - // Delete Owned Invite Codes List ownedCodes = db.InviteCodes.Where(i => i.Owner.Username == user.Username).ToList(); if (ownedCodes.Any()) @@ -696,15 +545,15 @@ namespace Teknik.Areas.Users.Utility } // Delete Auth Tokens - List authTokens = db.AuthTokens.Where(t => t.User.UserId == user.UserId).ToList(); - if (authTokens.Any()) - { - foreach (AuthToken authToken in authTokens) - { - db.AuthTokens.Remove(authToken); - } - db.SaveChanges(); - } + //List authTokens = db.AuthTokens.Where(t => t.User.UserId == user.UserId).ToList(); + //if (authTokens.Any()) + //{ + // foreach (AuthToken authToken in authTokens) + // { + // db.AuthTokens.Remove(authToken); + // } + // db.SaveChanges(); + //} // Delete User db.Users.Remove(user); @@ -716,30 +565,6 @@ namespace Teknik.Areas.Users.Utility } } - public static string CreateRecoveryEmailVerification(TeknikEntities db, Config config, User user) - { - // Check to see if there already is a verification code for the user - List verCodes = db.RecoveryEmailVerifications.Where(r => r.User.Username == user.Username).ToList(); - if (verCodes != null && verCodes.Any()) - { - foreach (RecoveryEmailVerification verCode in verCodes) - { - db.RecoveryEmailVerifications.Remove(verCode); - } - } - - // Create a new verification code and add it - string verifyCode = StringHelper.RandomString(24); - RecoveryEmailVerification ver = new RecoveryEmailVerification(); - ver.UserId = user.UserId; - ver.Code = verifyCode; - ver.DateCreated = DateTime.Now; - db.RecoveryEmailVerifications.Add(ver); - db.SaveChanges(); - - return verifyCode; - } - public static void SendRecoveryEmailVerification(Config config, string username, string email, string resetUrl, string verifyUrl) { SmtpClient client = new SmtpClient(); @@ -768,55 +593,6 @@ If you recieved this email and you did not sign up for an account, please email client.Send(mail); } - public static bool VerifyRecoveryEmail(TeknikEntities db, Config config, string username, string code) - { - User user = GetUser(db, username); - RecoveryEmailVerification verCode = db.RecoveryEmailVerifications.Where(r => r.User.Username == username && r.Code == code).FirstOrDefault(); - if (verCode != null) - { - // We have a match, so clear out the verifications for that user - List verCodes = db.RecoveryEmailVerifications.Where(r => r.User.Username == username).ToList(); - if (verCodes != null && verCodes.Any()) - { - foreach (RecoveryEmailVerification ver in verCodes) - { - db.RecoveryEmailVerifications.Remove(ver); - } - } - // Update the user - user.SecuritySettings.RecoveryVerified = true; - db.Entry(user).State = EntityState.Modified; - db.SaveChanges(); - - return true; - } - return false; - } - - public static string CreateResetPasswordVerification(TeknikEntities db, Config config, User user) - { - // Check to see if there already is a verification code for the user - List verCodes = db.ResetPasswordVerifications.Where(r => r.User.Username == user.Username).ToList(); - if (verCodes != null && verCodes.Any()) - { - foreach (ResetPasswordVerification verCode in verCodes) - { - db.ResetPasswordVerifications.Remove(verCode); - } - } - - // Create a new verification code and add it - string verifyCode = StringHelper.RandomString(24); - ResetPasswordVerification ver = new ResetPasswordVerification(); - ver.UserId = user.UserId; - ver.Code = verifyCode; - ver.DateCreated = DateTime.Now; - db.ResetPasswordVerifications.Add(ver); - db.SaveChanges(); - - return verifyCode; - } - public static void SendResetPasswordVerification(Config config, string username, string email, string resetUrl) { SmtpClient client = new SmtpClient(); @@ -844,28 +620,6 @@ If you recieved this email and you did not reset your password, you can ignore t client.Send(mail); } - - public static bool VerifyResetPassword(TeknikEntities db, Config config, string username, string code) - { - User user = GetUser(db, username); - ResetPasswordVerification verCode = db.ResetPasswordVerifications.Where(r => r.User.Username == username && r.Code == code).FirstOrDefault(); - if (verCode != null) - { - // We have a match, so clear out the verifications for that user - List verCodes = db.ResetPasswordVerifications.Where(r => r.User.Username == username).ToList(); - if (verCodes != null && verCodes.Any()) - { - foreach (ResetPasswordVerification ver in verCodes) - { - db.ResetPasswordVerifications.Remove(ver); - } - } - db.SaveChanges(); - - return true; - } - return false; - } #endregion #region Email Management @@ -914,7 +668,7 @@ If you recieved this email and you did not reset your password, you can ignore t return lastActive; } - public static void AddUserEmail(Config config, string email, string password) + public static void CreateUserEmail(Config config, string email, string password) { try { @@ -1085,7 +839,7 @@ If you recieved this email and you did not reset your password, you can ignore t return lastActive; } - public static void AddUserGit(Config config, string username, string password) + public static void CreateUserGit(Config config, string username, string password) { try { @@ -1302,56 +1056,5 @@ If you recieved this email and you did not reset your password, you can ignore t } } #endregion - - public static ClaimsIdentity CreateClaimsIdentity(TeknikEntities db, string username) - { - User user = GetUser(db, username); - - if (user != null) - { - var claims = new List - { - new Claim(ClaimTypes.Name, user.Username) - }; - - // Add their roles - foreach (var role in user.UserRoles) - { - claims.Add(new Claim(ClaimTypes.Role, role.Role.Name)); - } - - return new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); - } - return null; - } - - public static Tuple CreateTrustedDeviceCookie(Config config, string username, string domain, bool local) - { - byte[] time = BitConverter.GetBytes(DateTime.UtcNow.ToBinary()); - byte[] key = Guid.NewGuid().ToByteArray(); - string token = Convert.ToBase64String(time.Concat(key).ToArray()); - var trustCookie = new CookieOptions() - { - HttpOnly = true, - Secure = true, - Expires = DateTime.Now.AddDays(30) - }; - - // Set domain dependent on where it's being ran from - if (local) // localhost - { - trustCookie.Domain = null; - } - else if (config.DevEnvironment) // dev.example.com - { - trustCookie.Domain = string.Format("dev.{0}", domain); - } - else // A production instance - { - trustCookie.Domain = string.Format(".{0}", domain); - } - - return new Tuple(trustCookie, token); - } } } diff --git a/Teknik/Areas/User/ViewModels/APIClientSettingsViewModel.cs b/Teknik/Areas/User/ViewModels/APIClientSettingsViewModel.cs new file mode 100644 index 0000000..0244e2e --- /dev/null +++ b/Teknik/Areas/User/ViewModels/APIClientSettingsViewModel.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Teknik.Areas.Users.ViewModels +{ + public class APIClientSettingsViewModel : SettingsViewModel + { + + public List AuthTokens { get; set; } + + public APIClientSettingsViewModel() + { + AuthTokens = new List(); + } + } +} diff --git a/Teknik/Areas/User/ViewModels/AccountSettingsViewModel.cs b/Teknik/Areas/User/ViewModels/AccountSettingsViewModel.cs new file mode 100644 index 0000000..0072a3f --- /dev/null +++ b/Teknik/Areas/User/ViewModels/AccountSettingsViewModel.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Teknik.Areas.Users.ViewModels +{ + public class AccountSettingsViewModel : SettingsViewModel + { + public string CurrentPassword { get; set; } + + public string NewPassword { get; set; } + + public string NewPasswordConfirm { get; set; } + } +} diff --git a/Teknik/Areas/User/ViewModels/ProfileViewModel.cs b/Teknik/Areas/User/ViewModels/ProfileViewModel.cs index 71b3a73..325b373 100644 --- a/Teknik/Areas/User/ViewModels/ProfileViewModel.cs +++ b/Teknik/Areas/User/ViewModels/ProfileViewModel.cs @@ -16,14 +16,8 @@ namespace Teknik.Areas.Users.ViewModels public string Email { get; set; } - public DateTime JoinDate { get; set; } - public DateTime LastSeen { get; set; } - public AccountType AccountType { get; set; } - - public AccountStatus AccountStatus { get; set; } - public List Uploads { get; set; } public List Pastes { get; set; } @@ -34,10 +28,10 @@ namespace Teknik.Areas.Users.ViewModels public UserSettings UserSettings { get; set; } - public SecuritySettings SecuritySettings { get; set; } - public BlogSettings BlogSettings { get; set; } public UploadSettings UploadSettings { get; set; } + + public IdentityUserInfo IdentityUserInfo { get; set; } } } diff --git a/Teknik/Areas/User/ViewModels/RegisterViewModel.cs b/Teknik/Areas/User/ViewModels/RegisterViewModel.cs index 94bdcfd..a2dd4a7 100644 --- a/Teknik/Areas/User/ViewModels/RegisterViewModel.cs +++ b/Teknik/Areas/User/ViewModels/RegisterViewModel.cs @@ -26,9 +26,6 @@ namespace Teknik.Areas.Users.ViewModels [Display(Name = "Recovery Email")] [DataType(DataType.EmailAddress)] public string RecoveryEmail { get; set; } - - [Display(Name = "Public PGP Key")] - public string PublicKey { get; set; } public string ReturnUrl { get; set; } } diff --git a/Teknik/Areas/User/ViewModels/SecuritySettingsViewModel.cs b/Teknik/Areas/User/ViewModels/SecuritySettingsViewModel.cs index f26d40f..166604e 100644 --- a/Teknik/Areas/User/ViewModels/SecuritySettingsViewModel.cs +++ b/Teknik/Areas/User/ViewModels/SecuritySettingsViewModel.cs @@ -5,12 +5,6 @@ namespace Teknik.Areas.Users.ViewModels { public class SecuritySettingsViewModel : SettingsViewModel { - public string CurrentPassword { get; set; } - - public string NewPassword { get; set; } - - public string NewPasswordConfirm { get; set; } - public string PgpPublicKey { get; set; } public string RecoveryEmail { get; set; } @@ -20,22 +14,13 @@ namespace Teknik.Areas.Users.ViewModels public bool AllowTrustedDevices { get; set; } public bool TwoFactorEnabled { get; set; } - - public string TwoFactorKey { get; set; } - - public int TrustedDeviceCount { get; set; } - - public List AuthTokens { get; set; } public SecuritySettingsViewModel() { - TrustedDeviceCount = 0; - AuthTokens = new List(); RecoveryEmail = string.Empty; RecoveryVerified = false; AllowTrustedDevices = false; TwoFactorEnabled = false; - TwoFactorKey = string.Empty; PgpPublicKey = string.Empty; } } diff --git a/Teknik/Areas/User/Views/User/Register.cshtml b/Teknik/Areas/User/Views/User/Register.cshtml index 2660cbd..aa002be 100644 --- a/Teknik/Areas/User/Views/User/Register.cshtml +++ b/Teknik/Areas/User/Views/User/Register.cshtml @@ -34,12 +34,6 @@ -
    - - -

    Username must meet the following requirements: @Config.UserConfig.UsernameFilterLabel
    diff --git a/Teknik/Areas/User/Views/User/ResetPassword.cshtml b/Teknik/Areas/User/Views/User/ResetPassword.cshtml index ffada66..e3b7ae7 100644 --- a/Teknik/Areas/User/Views/User/ResetPassword.cshtml +++ b/Teknik/Areas/User/Views/User/ResetPassword.cshtml @@ -10,7 +10,6 @@

    - Send a temporary link to your recovery email to reset your password.
    diff --git a/Teknik/Areas/User/Views/User/Settings/APIClientSettings.cshtml b/Teknik/Areas/User/Views/User/Settings/APIClientSettings.cshtml new file mode 100644 index 0000000..7d4a97b --- /dev/null +++ b/Teknik/Areas/User/Views/User/Settings/APIClientSettings.cshtml @@ -0,0 +1,45 @@ +@model Teknik.Areas.Users.ViewModels.APIClientSettingsViewModel + +@using Teknik.Areas.Users.ViewModels + +@{ + Layout = "~/Areas/User/Views/User/Settings/Settings.cshtml"; +} + + + +
    +
    +

    API Clients

    +
    +
    +
    +
    +
    +
    + +
    +
      + @if (Model.AuthTokens.Any()) + { + foreach (AuthTokenViewModel token in Model.AuthTokens) + { + @await Html.PartialAsync("Settings/AuthToken", token) + } + } + else + { +
    • No Authentication Tokens
    • + } +
    +
    +
    +
    + + diff --git a/Teknik/Areas/User/Views/User/Settings/AccountSettings.cshtml b/Teknik/Areas/User/Views/User/Settings/AccountSettings.cshtml new file mode 100644 index 0000000..8614455 --- /dev/null +++ b/Teknik/Areas/User/Views/User/Settings/AccountSettings.cshtml @@ -0,0 +1,68 @@ +@model Teknik.Areas.Users.ViewModels.AccountSettingsViewModel + +@using Teknik.Areas.Users.ViewModels + +@{ + Layout = "~/Areas/User/Views/User/Settings/Settings.cshtml"; +} + + + +
    +
    +

    Change Password

    +
    +
    +
    + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    + I forgot my password +
    +
    +
    +
    +

    Want to manage your two-factor authentication? Check out your Security settings.

    +
    +
    + + +
    +
    +

    Delete Account

    +
    +
    +
    +
    +
    + +
    +
    + + \ No newline at end of file diff --git a/Teknik/Areas/User/Views/User/Settings/BlogSettings.cshtml b/Teknik/Areas/User/Views/User/Settings/BlogSettings.cshtml index 12364bf..59ad56e 100644 --- a/Teknik/Areas/User/Views/User/Settings/BlogSettings.cshtml +++ b/Teknik/Areas/User/Views/User/Settings/BlogSettings.cshtml @@ -25,8 +25,8 @@

    - - + +
    diff --git a/Teknik/Areas/User/Views/User/Settings/ProfileSettings.cshtml b/Teknik/Areas/User/Views/User/Settings/ProfileSettings.cshtml index ea274fd..42cc770 100644 --- a/Teknik/Areas/User/Views/User/Settings/ProfileSettings.cshtml +++ b/Teknik/Areas/User/Views/User/Settings/ProfileSettings.cshtml @@ -30,8 +30,8 @@

    - - + +
    diff --git a/Teknik/Areas/User/Views/User/Settings/SecuritySettings.cshtml b/Teknik/Areas/User/Views/User/Settings/SecuritySettings.cshtml index 21abe13..cb1dc26 100644 --- a/Teknik/Areas/User/Views/User/Settings/SecuritySettings.cshtml +++ b/Teknik/Areas/User/Views/User/Settings/SecuritySettings.cshtml @@ -7,19 +7,14 @@ } - -
    @@ -71,106 +66,68 @@
    +
    +
    +

    Two-Factor Authentication - @(Model.TwoFactorEnabled ? Html.Raw("Enabled") : Html.Raw("Disabled"))

    +
    +
    +
    +
    +
    + @{ + if (Model.TwoFactorEnabled) + { +

    +   Reset Recovery Codes +

    + } + else + { +

    + +

    + } + } +
    +
    + +
    +
    +

    Security Info

    +
    +
    +
    -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    -
    -
    - - - @if (!string.IsNullOrEmpty(Model.RecoveryEmail)) +
    + + + @if (!string.IsNullOrEmpty(Model.RecoveryEmail)) + { +

    + @if (Model.RecoveryVerified) { -

    - @if (Model.RecoveryVerified) - { - Verified - } - else - { - Unverified Resend - } -

    - } -
    -
    -
    -
    - -
    - -
    - -
    -
    -
    -
    - -
    - -
    - -
    -
    -
    -
    -
    - -
    -
      - @if (Model.AuthTokens.Any()) - { - foreach (AuthTokenViewModel token in Model.AuthTokens) - { - @await Html.PartialAsync("Settings/AuthToken", token) - } + Verified } else { -
    • No Authentication Tokens
    • + Unverified Resend } -
    -
    +

    + }
    +
    +
    + + +
    +
    +
    -
    - - + +
    diff --git a/Teknik/Areas/User/Views/User/Settings/Settings.cshtml b/Teknik/Areas/User/Views/User/Settings/Settings.cshtml index dd8ff18..4f02f2b 100644 --- a/Teknik/Areas/User/Views/User/Settings/Settings.cshtml +++ b/Teknik/Areas/User/Views/User/Settings/Settings.cshtml @@ -6,11 +6,6 @@ @using Teknik.Areas.Users.ViewModels - -
    @if (!Model.Error) { @@ -19,17 +14,16 @@ -
    - -
    @RenderBody() diff --git a/Teknik/Areas/User/Views/User/Settings/UploadSettings.cshtml b/Teknik/Areas/User/Views/User/Settings/UploadSettings.cshtml index 806c61b..e65202c 100644 --- a/Teknik/Areas/User/Views/User/Settings/UploadSettings.cshtml +++ b/Teknik/Areas/User/Views/User/Settings/UploadSettings.cshtml @@ -7,7 +7,6 @@ } @@ -27,8 +26,8 @@

    - - + +
    diff --git a/Teknik/Areas/User/Views/User/ViewProfile.cshtml b/Teknik/Areas/User/Views/User/ViewProfile.cshtml index 7d8ffa2..779d311 100644 --- a/Teknik/Areas/User/Views/User/ViewProfile.cshtml +++ b/Teknik/Areas/User/Views/User/ViewProfile.cshtml @@ -22,10 +22,10 @@ string pgpFingerprint = pgpFingerprint = string.Empty; string pgpFingerprint64 = string.Empty; - if (!string.IsNullOrEmpty(Model.SecuritySettings.PGPSignature)) + if (!string.IsNullOrEmpty(Model.IdentityUserInfo.PGPPublicKey)) { - pgpFingerprint = PGP.GetFingerprint(Model.SecuritySettings.PGPSignature); - pgpFingerprint64 = PGP.GetFingerprint64(Model.SecuritySettings.PGPSignature); + pgpFingerprint = PGP.GetFingerprint(Model.IdentityUserInfo.PGPPublicKey); + pgpFingerprint64 = PGP.GetFingerprint64(Model.IdentityUserInfo.PGPPublicKey); }
    @@ -72,7 +72,7 @@
    - +
    @@ -83,18 +83,18 @@
      -
    • Joined
    • +
    • Joined
    • @if (OwnProfile) { -
    • Last Seen
    • +
    • Last Seen
    • } @if (OwnProfile) { -
    • Account Type @Model.AccountType
    • +
    • Account Type @Model.IdentityUserInfo.AccountType
    • } @if (User.IsInRole("Admin")) { -
    • Account Status @Model.AccountStatus
    • +
    • Account Status @Model.IdentityUserInfo.AccountStatus
    • } @if (!string.IsNullOrEmpty(pgpFingerprint)) { diff --git a/Teknik/Areas/User/Views/User/_LoginModalPartial.cshtml b/Teknik/Areas/User/Views/User/_LoginModalPartial.cshtml index f7ff132..58b3622 100644 --- a/Teknik/Areas/User/Views/User/_LoginModalPartial.cshtml +++ b/Teknik/Areas/User/Views/User/_LoginModalPartial.cshtml @@ -14,20 +14,3 @@
    } - -@if (Config.UserConfig.LoginEnabled) -{ - -} diff --git a/Teknik/Areas/User/Views/User/_LoginPartial.cshtml b/Teknik/Areas/User/Views/User/_LoginPartial.cshtml index b9a5615..626c1ff 100644 --- a/Teknik/Areas/User/Views/User/_LoginPartial.cshtml +++ b/Teknik/Areas/User/Views/User/_LoginPartial.cshtml @@ -7,10 +7,10 @@ @User.Identity.Name @@ -31,17 +31,15 @@ { if (Config.UserConfig.RegistrationEnabled) { - + } if (Config.UserConfig.LoginEnabled) { - - + @**@ + Log In } }
    diff --git a/Teknik/Areas/Vault/Controllers/VaultController.cs b/Teknik/Areas/Vault/Controllers/VaultController.cs index e157a9f..c656911 100644 --- a/Teknik/Areas/Vault/Controllers/VaultController.cs +++ b/Teknik/Areas/Vault/Controllers/VaultController.cs @@ -23,7 +23,7 @@ using Teknik.Utilities; namespace Teknik.Areas.Vault.Controllers { - [TeknikAuthorize] + [Authorize] [Area("Vault")] public class VaultController : DefaultController { diff --git a/Teknik/Attributes/TeknikAuthorizeAttribute.cs b/Teknik/Attributes/TeknikAuthorizeAttribute.cs index 57bd419..8bc50f9 100644 --- a/Teknik/Attributes/TeknikAuthorizeAttribute.cs +++ b/Teknik/Attributes/TeknikAuthorizeAttribute.cs @@ -12,6 +12,10 @@ using Teknik.Configuration; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Logging; +using Teknik.Logging; +using Teknik.Data; +using Teknik.Security; namespace Teknik.Attributes { @@ -41,193 +45,25 @@ namespace Teknik.Attributes if (!user.Identity.IsAuthenticated) { - // it isn't needed to set unauthorized result - // as the base class already requires the user to be authenticated - // this also makes redirect to a login page work properly - // context.Result = new UnauthorizedResult(); - return; + //if (m_AuthType == AuthType.Forms) + //{ + // var logger = (ILogger)context.HttpContext.RequestServices.GetService(typeof(ILogger)); + // var config = (Config)context.HttpContext.RequestServices.GetService(typeof(Config)); + // var dbContext = (TeknikEntities)context.HttpContext.RequestServices.GetService(typeof(TeknikEntities)); + // var logoutSession = (LogoutSessionManager)context.HttpContext.RequestServices.GetService(typeof(LogoutSessionManager)); + + // var userController = new UserController(logger, config, dbContext, logoutSession); + // if (userController != null) + // { + // // auth failed, redirect to login page + // var request = context.HttpContext.Request; + // string redirectUrl = (request.Host != null && request.Path != null) ? request.Host + request.Path : string.Empty; + + // context.Result = userController.Login(redirectUrl); + // return; + // } + //} } - - //// you can also use registered services - //var someService = context.HttpContext.RequestServices.GetService(); - - //var isAuthorized = someService.IsUserAuthorized(user.Identity.Name, _someFilterParameter); - //if (!isAuthorized) - //{ - // context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.Forbidden); - // return; - //} } - - //public override void OnAuthorization(AuthorizationContext filterContext) - //{ - // if (filterContext == null) - // { - // throw new ArgumentNullException("filterContext"); - // } - - // if (OutputCacheAttribute.IsChildActionCacheActive(filterContext)) - // { - // // If a child action cache block is active, we need to fail immediately, even if authorization - // // would have succeeded. The reason is that there's no way to hook a callback to rerun - // // authorization before the fragment is served from the cache, so we can't guarantee that this - // // filter will be re-run on subsequent requests. - // throw new InvalidOperationException("AuthorizeAttribute cannot be used within a child action caching block."); - // } - - // // Check to see if we want to skip Authentication Check - // bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true) - // || filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true); - // if (skipAuthorization) - // return; - - // // Check the users auth - // if (AuthorizeCore(filterContext.HttpContext)) - // { - // // ** IMPORTANT ** - // // Since we're performing authorization at the action level, the authorization code runs - // // after the output caching module. In the worst case this could allow an authorized user - // // to cause the page to be cached, then an unauthorized user would later be served the - // // cached page. We work around this by telling proxies not to cache the sensitive page, - // // then we hook our custom authorization code into the caching mechanism so that we have - // // the final say on whether a page should be served from the cache. - - // HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache; - // cachePolicy.SetProxyMaxAge(new TimeSpan(0)); - // cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */); - - // return; - // } - // else if (!filterContext.HttpContext.User.Identity.IsAuthenticated) - // { - // HandleUnauthorizedRequest(filterContext); - // } - // else - // { - // // uh oh, they are authorized, but don't have access. ABORT ABORT ABORT - // HandleInvalidAuthRequest(filterContext); - // } - //} - - //protected override bool AuthorizeCore(HttpContextBase httpContext) - //{ - // switch (m_AuthType) - // { - // case AuthType.Basic: - // #region Basic Authentication - // // Let's see if they have an auth token - // if (httpContext.Request != null) - // { - // if (httpContext.Request.Headers.HasKeys()) - // { - // string auth = httpContext.Request.Headers["Authorization"]; - // if (!string.IsNullOrEmpty(auth)) - // { - // string[] parts = auth.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - // string type = string.Empty; - // string value = string.Empty; - // if (parts.Length > 0) - // { - // type = parts[0].ToLower(); - // } - // if (parts.Length > 1) - // { - // value = parts[1]; - // } - - // using (TeknikEntities entities = new TeknikEntities()) - // { - // // Get the user information based on the auth type - // switch (type) - // { - // case "basic": - // KeyValuePair authCreds = StringHelper.ParseBasicAuthHeader(value); - // bool validToken = UserHelper.UserTokenCorrect(entities, authCreds.Key, authCreds.Value); - - // if (validToken) - // { - // User user = UserHelper.GetUserFromToken(entities, authCreds.Key, authCreds.Value); - - // return UserHelper.UserHasRoles(user, Roles); - // } - // break; - // default: - // break; - // } - // } - // } - // } - // } - // #endregion - // return false; - // case AuthType.Forms: - // return base.AuthorizeCore(httpContext); - // default: - // return base.AuthorizeCore(httpContext); - // } - //} - - //protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) - //{ - // ActionResult result = new HttpUnauthorizedResult(); - // switch (m_AuthType) - // { - // case AuthType.Basic: - // Config config = Config.Load(); - // filterContext.HttpContext.Response.Clear(); - // filterContext.HttpContext.Response.AddHeader("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", config.Title)); - // filterContext.HttpContext.Response.StatusCode = 401; - // filterContext.HttpContext.Response.TrySkipIisCustomErrors = true; - // filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true; - // result = new JsonResult(new { error = new { type = 401, message = "Unauthorized" } }); - // break; - // case AuthType.Forms: - // var userController = new UserController(); - // if (userController != null) - // { - // // auth failed, redirect to login page - // var request = filterContext.HttpContext.Request; - // string redirectUrl = (request.Url != null) ? filterContext.HttpContext.Request.Url.AbsoluteUri.ToString() : string.Empty; - - // result = userController.Login(redirectUrl); - // } - // break; - // default: - // break; - // } - - // filterContext.Result = result; - //} - - //protected void HandleInvalidAuthRequest(AuthorizationContext filterContext) - //{ - // ActionResult result = new HttpUnauthorizedResult(); - // switch (m_AuthType) - // { - // case AuthType.Basic: - // filterContext.HttpContext.Response.Clear(); - // filterContext.HttpContext.Response.StatusCode = 403; - // filterContext.HttpContext.Response.TrySkipIisCustomErrors = true; - // filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true; - // result = new JsonResult(new { error = new { type = 403, message = "Not Authorized" } }); - // break; - // case AuthType.Forms: - // var errorController = new ErrorController(); - // if (errorController != null) - // { - // result = errorController.Http403(new Exception("Not Authorized")); - // } - // break; - // default: - // break; - // } - - // filterContext.Result = result; - //} - - //private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus) - //{ - // validationStatus = base.OnCacheAuthorization(new HttpContextWrapper(context)); - //} } } diff --git a/Teknik/Controllers/DefaultController.cs b/Teknik/Controllers/DefaultController.cs index 3052e4b..2e160ba 100644 --- a/Teknik/Controllers/DefaultController.cs +++ b/Teknik/Controllers/DefaultController.cs @@ -11,6 +11,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; using System; using System.IO; +using System.Reflection; using System.Threading.Tasks; using System.Web; using Teknik.Areas.Error.Controllers; @@ -19,7 +20,6 @@ using Teknik.Configuration; using Teknik.Data; using Teknik.Filters; using Teknik.Logging; -using Teknik.Security; using Teknik.Utilities; namespace Teknik.Controllers @@ -106,8 +106,9 @@ namespace Teknik.Controllers using (var writer = new StringWriter()) { + string path = (new Uri(Assembly.GetExecutingAssembly().CodeBase)).AbsolutePath; ViewEngineResult viewResult = - viewEngine.FindView(ControllerContext, viewName, false); + viewEngine.GetView(path, viewName, false); ViewContext viewContext = new ViewContext( ControllerContext, diff --git a/Teknik/Migrations/20180624191511_UserLoginInfo.Designer.cs b/Teknik/Data/Migrations/20180624191511_UserLoginInfo.Designer.cs similarity index 99% rename from Teknik/Migrations/20180624191511_UserLoginInfo.Designer.cs rename to Teknik/Data/Migrations/20180624191511_UserLoginInfo.Designer.cs index 084b856..2daf15d 100644 --- a/Teknik/Migrations/20180624191511_UserLoginInfo.Designer.cs +++ b/Teknik/Data/Migrations/20180624191511_UserLoginInfo.Designer.cs @@ -7,7 +7,7 @@ using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Teknik.Data; -namespace Teknik.Migrations +namespace Teknik.Data.Migrations { [DbContext(typeof(TeknikEntities))] [Migration("20180624191511_UserLoginInfo")] diff --git a/Teknik/Migrations/20180624191511_UserLoginInfo.cs b/Teknik/Data/Migrations/20180624191511_UserLoginInfo.cs similarity index 97% rename from Teknik/Migrations/20180624191511_UserLoginInfo.cs rename to Teknik/Data/Migrations/20180624191511_UserLoginInfo.cs index 0481095..674cc15 100644 --- a/Teknik/Migrations/20180624191511_UserLoginInfo.cs +++ b/Teknik/Data/Migrations/20180624191511_UserLoginInfo.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; -namespace Teknik.Migrations +namespace Teknik.Data.Migrations { public partial class UserLoginInfo : Migration { diff --git a/Teknik/Migrations/TeknikEntitiesModelSnapshot.cs b/Teknik/Data/Migrations/20181018071735_IdentityAuth.Designer.cs similarity index 69% rename from Teknik/Migrations/TeknikEntitiesModelSnapshot.cs rename to Teknik/Data/Migrations/20181018071735_IdentityAuth.Designer.cs index a20d780..7cc31a8 100644 --- a/Teknik/Migrations/TeknikEntitiesModelSnapshot.cs +++ b/Teknik/Data/Migrations/20181018071735_IdentityAuth.Designer.cs @@ -3,19 +3,21 @@ using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Teknik.Data; -namespace Teknik.Migrations +namespace Teknik.Data.Migrations { [DbContext(typeof(TeknikEntities))] - partial class TeknikEntitiesModelSnapshot : ModelSnapshot + [Migration("20181018071735_IdentityAuth")] + partial class IdentityAuth { - protected override void BuildModel(ModelBuilder modelBuilder) + protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "2.1.0-rtm-30799") + .HasAnnotation("ProductVersion", "2.2.0-preview2-35157") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); @@ -382,43 +384,6 @@ namespace Teknik.Migrations b.ToTable("Uploads"); }); - modelBuilder.Entity("Teknik.Areas.Users.Models.AuthToken", b => - { - b.Property("AuthTokenId") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("HashedToken") - .HasAnnotation("CaseSensitive", true); - - b.Property("LastDateUsed"); - - b.Property("Name"); - - b.Property("UserId"); - - b.HasKey("AuthTokenId"); - - b.HasIndex("UserId"); - - b.ToTable("AuthTokens"); - }); - - modelBuilder.Entity("Teknik.Areas.Users.Models.BlogSettings", b => - { - b.Property("UserId") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Description"); - - b.Property("Title"); - - b.HasKey("UserId"); - - b.ToTable("Users"); - }); - modelBuilder.Entity("Teknik.Areas.Users.Models.InviteCode", b => { b.Property("InviteCodeId") @@ -506,97 +471,12 @@ namespace Teknik.Migrations b.ToTable("ResetPasswordVerifications"); }); - modelBuilder.Entity("Teknik.Areas.Users.Models.Role", b => - { - b.Property("RoleId") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Description"); - - b.Property("Name"); - - b.HasKey("RoleId"); - - b.ToTable("Roles"); - }); - - modelBuilder.Entity("Teknik.Areas.Users.Models.SecuritySettings", b => - { - b.Property("UserId") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("AllowTrustedDevices"); - - b.Property("PGPSignature"); - - b.Property("RecoveryEmail"); - - b.Property("RecoveryVerified"); - - b.Property("TwoFactorEnabled"); - - b.Property("TwoFactorKey") - .HasAnnotation("CaseSensitive", true); - - b.HasKey("UserId"); - - b.ToTable("Users"); - }); - - modelBuilder.Entity("Teknik.Areas.Users.Models.TrustedDevice", b => - { - b.Property("TrustedDeviceId") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("DateSeen"); - - b.Property("Name"); - - b.Property("Token") - .HasAnnotation("CaseSensitive", true); - - b.Property("UserId"); - - b.HasKey("TrustedDeviceId"); - - b.HasIndex("UserId"); - - b.ToTable("TrustedDevices"); - }); - - modelBuilder.Entity("Teknik.Areas.Users.Models.UploadSettings", b => - { - b.Property("UserId") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Encrypt"); - - b.HasKey("UserId"); - - b.ToTable("Users"); - }); - modelBuilder.Entity("Teknik.Areas.Users.Models.User", b => { b.Property("UserId") .ValueGeneratedOnAdd() .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("AccountStatus"); - - b.Property("AccountType"); - - b.Property("HashedPassword") - .HasAnnotation("CaseSensitive", true); - - b.Property("JoinDate"); - - b.Property("LastSeen"); - b.Property("Username"); b.HasKey("UserId"); @@ -604,42 +484,6 @@ namespace Teknik.Migrations b.ToTable("Users"); }); - modelBuilder.Entity("Teknik.Areas.Users.Models.UserRole", b => - { - b.Property("UserRoleId") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("RoleId"); - - b.Property("UserId"); - - b.HasKey("UserRoleId"); - - b.HasIndex("RoleId"); - - b.HasIndex("UserId"); - - b.ToTable("UserRoles"); - }); - - modelBuilder.Entity("Teknik.Areas.Users.Models.UserSettings", b => - { - b.Property("UserId") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("About"); - - b.Property("Quote"); - - b.Property("Website"); - - b.HasKey("UserId"); - - b.ToTable("Users"); - }); - modelBuilder.Entity("Teknik.Areas.Vault.Models.Vault", b => { b.Property("VaultId") @@ -693,27 +537,6 @@ namespace Teknik.Migrations b.HasDiscriminator("Discriminator").HasValue("VaultItem"); }); - modelBuilder.Entity("Teknik.Models.TransferType", b => - { - b.Property("TransferTypeId") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("PasteId"); - - b.Property("Type"); - - b.Property("UserId"); - - b.HasKey("TransferTypeId"); - - b.HasIndex("PasteId"); - - b.HasIndex("UserId"); - - b.ToTable("TransferTypes"); - }); - modelBuilder.Entity("Teknik.Areas.Vault.Models.PasteVaultItem", b => { b.HasBaseType("Teknik.Areas.Vault.Models.VaultItem"); @@ -722,8 +545,6 @@ namespace Teknik.Migrations b.HasIndex("PasteId"); - b.ToTable("PasteVaultItem"); - b.HasDiscriminator().HasValue("PasteVaultItem"); }); @@ -735,8 +556,6 @@ namespace Teknik.Migrations b.HasIndex("UploadId"); - b.ToTable("UploadVaultItem"); - b.HasDiscriminator().HasValue("UploadVaultItem"); }); @@ -830,22 +649,6 @@ namespace Teknik.Migrations .HasForeignKey("UserId"); }); - modelBuilder.Entity("Teknik.Areas.Users.Models.AuthToken", b => - { - b.HasOne("Teknik.Areas.Users.Models.User", "User") - .WithMany("AuthTokens") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Teknik.Areas.Users.Models.BlogSettings", b => - { - b.HasOne("Teknik.Areas.Users.Models.UploadSettings", "UploadSettings") - .WithOne("BlogSettings") - .HasForeignKey("Teknik.Areas.Users.Models.BlogSettings", "UserId") - .OnDelete(DeleteBehavior.Cascade); - }); - modelBuilder.Entity("Teknik.Areas.Users.Models.InviteCode", b => { b.HasOne("Teknik.Areas.Users.Models.User", "ClaimedUser") @@ -881,79 +684,73 @@ namespace Teknik.Migrations .OnDelete(DeleteBehavior.Cascade); }); - modelBuilder.Entity("Teknik.Areas.Users.Models.SecuritySettings", b => - { - b.HasOne("Teknik.Areas.Users.Models.BlogSettings", "BlogSettings") - .WithOne("SecuritySettings") - .HasForeignKey("Teknik.Areas.Users.Models.SecuritySettings", "UserId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Teknik.Areas.Users.Models.UploadSettings", "UploadSettings") - .WithOne("SecuritySettings") - .HasForeignKey("Teknik.Areas.Users.Models.SecuritySettings", "UserId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Teknik.Areas.Users.Models.TrustedDevice", b => - { - b.HasOne("Teknik.Areas.Users.Models.User", "User") - .WithMany("TrustedDevices") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - }); - modelBuilder.Entity("Teknik.Areas.Users.Models.User", b => { - b.HasOne("Teknik.Areas.Users.Models.BlogSettings", "BlogSettings") - .WithOne("User") - .HasForeignKey("Teknik.Areas.Users.Models.User", "UserId") - .OnDelete(DeleteBehavior.Cascade); + b.OwnsOne("Teknik.Areas.Users.Models.BlogSettings", "BlogSettings", b1 => + { + b1.Property("UserId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.HasOne("Teknik.Areas.Users.Models.SecuritySettings", "SecuritySettings") - .WithOne("User") - .HasForeignKey("Teknik.Areas.Users.Models.User", "UserId") - .OnDelete(DeleteBehavior.Cascade); + b1.Property("Description") + .HasColumnName("Description"); - b.HasOne("Teknik.Areas.Users.Models.UploadSettings", "UploadSettings") - .WithOne("User") - .HasForeignKey("Teknik.Areas.Users.Models.User", "UserId") - .OnDelete(DeleteBehavior.Cascade); + b1.Property("Title") + .HasColumnName("Title"); - b.HasOne("Teknik.Areas.Users.Models.UserSettings", "UserSettings") - .WithOne("User") - .HasForeignKey("Teknik.Areas.Users.Models.User", "UserId") - .OnDelete(DeleteBehavior.Cascade); - }); + b1.HasKey("UserId"); - modelBuilder.Entity("Teknik.Areas.Users.Models.UserRole", b => - { - b.HasOne("Teknik.Areas.Users.Models.Role", "Role") - .WithMany("UserRoles") - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade); + b1.ToTable("Users"); - b.HasOne("Teknik.Areas.Users.Models.User", "User") - .WithMany("UserRoles") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - }); + b1.HasOne("Teknik.Areas.Users.Models.User") + .WithOne("BlogSettings") + .HasForeignKey("Teknik.Areas.Users.Models.BlogSettings", "UserId") + .OnDelete(DeleteBehavior.Cascade); + }); - modelBuilder.Entity("Teknik.Areas.Users.Models.UserSettings", b => - { - b.HasOne("Teknik.Areas.Users.Models.BlogSettings", "BlogSettings") - .WithOne("UserSettings") - .HasForeignKey("Teknik.Areas.Users.Models.UserSettings", "UserId") - .OnDelete(DeleteBehavior.Cascade); + b.OwnsOne("Teknik.Areas.Users.Models.UploadSettings", "UploadSettings", b1 => + { + b1.Property("UserId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.HasOne("Teknik.Areas.Users.Models.SecuritySettings", "SecuritySettings") - .WithOne("UserSettings") - .HasForeignKey("Teknik.Areas.Users.Models.UserSettings", "UserId") - .OnDelete(DeleteBehavior.Cascade); + b1.Property("Encrypt") + .HasColumnName("Encrypt"); - b.HasOne("Teknik.Areas.Users.Models.UploadSettings", "UploadSettings") - .WithOne("UserSettings") - .HasForeignKey("Teknik.Areas.Users.Models.UserSettings", "UserId") - .OnDelete(DeleteBehavior.Cascade); + b1.HasKey("UserId"); + + b1.ToTable("Users"); + + b1.HasOne("Teknik.Areas.Users.Models.User") + .WithOne("UploadSettings") + .HasForeignKey("Teknik.Areas.Users.Models.UploadSettings", "UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + b.OwnsOne("Teknik.Areas.Users.Models.UserSettings", "UserSettings", b1 => + { + b1.Property("UserId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b1.Property("About") + .HasColumnName("About"); + + b1.Property("Quote") + .HasColumnName("Quote"); + + b1.Property("Website") + .HasColumnName("Website"); + + b1.HasKey("UserId"); + + b1.ToTable("Users"); + + b1.HasOne("Teknik.Areas.Users.Models.User") + .WithOne("UserSettings") + .HasForeignKey("Teknik.Areas.Users.Models.UserSettings", "UserId") + .OnDelete(DeleteBehavior.Cascade); + }); }); modelBuilder.Entity("Teknik.Areas.Vault.Models.Vault", b => @@ -971,17 +768,6 @@ namespace Teknik.Migrations .OnDelete(DeleteBehavior.Cascade); }); - modelBuilder.Entity("Teknik.Models.TransferType", b => - { - b.HasOne("Teknik.Areas.Paste.Models.Paste", "Paste") - .WithMany("Transfers") - .HasForeignKey("PasteId"); - - b.HasOne("Teknik.Areas.Users.Models.User", "User") - .WithMany("Transfers") - .HasForeignKey("UserId"); - }); - modelBuilder.Entity("Teknik.Areas.Vault.Models.PasteVaultItem", b => { b.HasOne("Teknik.Areas.Paste.Models.Paste", "Paste") diff --git a/Teknik/Data/Migrations/20181018071735_IdentityAuth.cs b/Teknik/Data/Migrations/20181018071735_IdentityAuth.cs new file mode 100644 index 0000000..2c0224c --- /dev/null +++ b/Teknik/Data/Migrations/20181018071735_IdentityAuth.cs @@ -0,0 +1,277 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Teknik.Data.Migrations +{ + public partial class IdentityAuth : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AuthTokens"); + + migrationBuilder.DropTable( + name: "TransferTypes"); + + migrationBuilder.DropTable( + name: "TrustedDevices"); + + migrationBuilder.DropTable( + name: "UserRoles"); + + migrationBuilder.DropTable( + name: "Roles"); + + migrationBuilder.DropColumn( + name: "AllowTrustedDevices", + table: "Users"); + + migrationBuilder.DropColumn( + name: "PGPSignature", + table: "Users"); + + migrationBuilder.DropColumn( + name: "RecoveryEmail", + table: "Users"); + + migrationBuilder.DropColumn( + name: "RecoveryVerified", + table: "Users"); + + migrationBuilder.DropColumn( + name: "TwoFactorEnabled", + table: "Users"); + + migrationBuilder.DropColumn( + name: "TwoFactorKey", + table: "Users"); + + migrationBuilder.DropColumn( + name: "AccountStatus", + table: "Users"); + + migrationBuilder.DropColumn( + name: "AccountType", + table: "Users"); + + migrationBuilder.DropColumn( + name: "HashedPassword", + table: "Users"); + + migrationBuilder.DropColumn( + name: "JoinDate", + table: "Users"); + + migrationBuilder.DropColumn( + name: "LastSeen", + table: "Users"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AllowTrustedDevices", + table: "Users", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "PGPSignature", + table: "Users", + nullable: true); + + migrationBuilder.AddColumn( + name: "RecoveryEmail", + table: "Users", + nullable: true); + + migrationBuilder.AddColumn( + name: "RecoveryVerified", + table: "Users", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "TwoFactorEnabled", + table: "Users", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "TwoFactorKey", + table: "Users", + nullable: true); + + migrationBuilder.AddColumn( + name: "AccountStatus", + table: "Users", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "AccountType", + table: "Users", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "HashedPassword", + table: "Users", + nullable: true); + + migrationBuilder.AddColumn( + name: "JoinDate", + table: "Users", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "LastSeen", + table: "Users", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.CreateTable( + name: "AuthTokens", + columns: table => new + { + AuthTokenId = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + HashedToken = table.Column(nullable: true), + LastDateUsed = table.Column(nullable: true), + Name = table.Column(nullable: true), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AuthTokens", x => x.AuthTokenId); + table.ForeignKey( + name: "FK_AuthTokens_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "UserId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Roles", + columns: table => new + { + RoleId = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Description = table.Column(nullable: true), + Name = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Roles", x => x.RoleId); + }); + + migrationBuilder.CreateTable( + name: "TransferTypes", + columns: table => new + { + TransferTypeId = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + PasteId = table.Column(nullable: true), + Type = table.Column(nullable: false), + UserId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_TransferTypes", x => x.TransferTypeId); + table.ForeignKey( + name: "FK_TransferTypes_Pastes_PasteId", + column: x => x.PasteId, + principalTable: "Pastes", + principalColumn: "PasteId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_TransferTypes_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "TrustedDevices", + columns: table => new + { + TrustedDeviceId = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + DateSeen = table.Column(nullable: false), + Name = table.Column(nullable: true), + Token = table.Column(nullable: true), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TrustedDevices", x => x.TrustedDeviceId); + table.ForeignKey( + name: "FK_TrustedDevices_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "UserId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "UserRoles", + columns: table => new + { + UserRoleId = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + RoleId = table.Column(nullable: false), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserRoles", x => x.UserRoleId); + table.ForeignKey( + name: "FK_UserRoles_Roles_RoleId", + column: x => x.RoleId, + principalTable: "Roles", + principalColumn: "RoleId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_UserRoles_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "UserId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AuthTokens_UserId", + table: "AuthTokens", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_TransferTypes_PasteId", + table: "TransferTypes", + column: "PasteId"); + + migrationBuilder.CreateIndex( + name: "IX_TransferTypes_UserId", + table: "TransferTypes", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_TrustedDevices_UserId", + table: "TrustedDevices", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_UserRoles_RoleId", + table: "UserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "IX_UserRoles_UserId", + table: "UserRoles", + column: "UserId"); + } + } +} diff --git a/Teknik/Data/Migrations/20181021081119_RemovedVerificationTables.Designer.cs b/Teknik/Data/Migrations/20181021081119_RemovedVerificationTables.Designer.cs new file mode 100644 index 0000000..76d4c1f --- /dev/null +++ b/Teknik/Data/Migrations/20181021081119_RemovedVerificationTables.Designer.cs @@ -0,0 +1,733 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Teknik.Data; + +namespace Teknik.Data.Migrations +{ + [DbContext(typeof(TeknikEntities))] + [Migration("20181021081119_RemovedVerificationTables")] + partial class RemovedVerificationTables + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.0-preview2-35157") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.Blog", b => + { + b.Property("BlogId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("UserId"); + + b.HasKey("BlogId"); + + b.HasIndex("UserId"); + + b.ToTable("Blogs"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPost", b => + { + b.Property("BlogPostId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Article"); + + b.Property("BlogId"); + + b.Property("DateEdited"); + + b.Property("DatePosted"); + + b.Property("DatePublished"); + + b.Property("Published"); + + b.Property("System"); + + b.Property("Title"); + + b.HasKey("BlogPostId"); + + b.HasIndex("BlogId"); + + b.ToTable("BlogPosts"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPostComment", b => + { + b.Property("BlogPostCommentId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Article"); + + b.Property("BlogPostId"); + + b.Property("DateEdited"); + + b.Property("DatePosted"); + + b.Property("UserId"); + + b.HasKey("BlogPostCommentId"); + + b.HasIndex("BlogPostId"); + + b.HasIndex("UserId"); + + b.ToTable("BlogPostComments"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPostTag", b => + { + b.Property("BlogPostTagId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("BlogPostId"); + + b.Property("Description"); + + b.Property("Name"); + + b.HasKey("BlogPostTagId"); + + b.HasIndex("BlogPostId"); + + b.ToTable("BlogPostTags"); + }); + + modelBuilder.Entity("Teknik.Areas.Contact.Models.Contact", b => + { + b.Property("ContactId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("DateAdded"); + + b.Property("Email"); + + b.Property("Message"); + + b.Property("Name"); + + b.Property("Subject"); + + b.HasKey("ContactId"); + + b.ToTable("Contact"); + }); + + modelBuilder.Entity("Teknik.Areas.Paste.Models.Paste", b => + { + b.Property("PasteId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("BlockSize"); + + b.Property("Content"); + + b.Property("DatePosted"); + + b.Property("ExpireDate"); + + b.Property("HashedPassword") + .HasAnnotation("CaseSensitive", true); + + b.Property("Hide"); + + b.Property("IV") + .HasAnnotation("CaseSensitive", true); + + b.Property("Key") + .HasAnnotation("CaseSensitive", true); + + b.Property("KeySize"); + + b.Property("MaxViews"); + + b.Property("Syntax"); + + b.Property("Title"); + + b.Property("Url") + .HasAnnotation("CaseSensitive", true); + + b.Property("UserId"); + + b.Property("Views"); + + b.HasKey("PasteId"); + + b.HasIndex("UserId"); + + b.ToTable("Pastes"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.Podcast", b => + { + b.Property("PodcastId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("DateEdited"); + + b.Property("DatePosted"); + + b.Property("DatePublished"); + + b.Property("Description"); + + b.Property("Episode"); + + b.Property("Published"); + + b.Property("Title"); + + b.HasKey("PodcastId"); + + b.ToTable("Podcasts"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastComment", b => + { + b.Property("PodcastCommentId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Article"); + + b.Property("DateEdited"); + + b.Property("DatePosted"); + + b.Property("PodcastId"); + + b.Property("UserId"); + + b.HasKey("PodcastCommentId"); + + b.HasIndex("PodcastId"); + + b.HasIndex("UserId"); + + b.ToTable("PodcastComments"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastFile", b => + { + b.Property("PodcastFileId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ContentLength"); + + b.Property("ContentType"); + + b.Property("FileName"); + + b.Property("Path"); + + b.Property("PodcastId"); + + b.Property("Size"); + + b.HasKey("PodcastFileId"); + + b.HasIndex("PodcastId"); + + b.ToTable("PodcastFiles"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastTag", b => + { + b.Property("PodcastTagId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description"); + + b.Property("Name"); + + b.Property("PodcastId"); + + b.HasKey("PodcastTagId"); + + b.HasIndex("PodcastId"); + + b.ToTable("PodcastTags"); + }); + + modelBuilder.Entity("Teknik.Areas.Shortener.Models.ShortenedUrl", b => + { + b.Property("ShortenedUrlId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("DateAdded"); + + b.Property("OriginalUrl"); + + b.Property("ShortUrl") + .HasAnnotation("CaseSensitive", true); + + b.Property("UserId"); + + b.Property("Views"); + + b.HasKey("ShortenedUrlId"); + + b.HasIndex("UserId"); + + b.ToTable("ShortenedUrls"); + }); + + modelBuilder.Entity("Teknik.Areas.Stats.Models.Takedown", b => + { + b.Property("TakedownId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ActionTaken"); + + b.Property("DateActionTaken"); + + b.Property("DateRequested"); + + b.Property("Reason"); + + b.Property("Requester"); + + b.Property("RequesterContact"); + + b.HasKey("TakedownId"); + + b.ToTable("Takedowns"); + }); + + modelBuilder.Entity("Teknik.Areas.Stats.Models.Transaction", b => + { + b.Property("TransactionId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Amount") + .HasColumnType("decimal(19, 5)"); + + b.Property("Currency"); + + b.Property("DateSent"); + + b.Property("Reason"); + + b.HasKey("TransactionId"); + + b.ToTable("Transactions"); + }); + + modelBuilder.Entity("Teknik.Areas.Upload.Models.Upload", b => + { + b.Property("UploadId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("BlockSize"); + + b.Property("ContentLength"); + + b.Property("ContentType"); + + b.Property("DateUploaded"); + + b.Property("DeleteKey") + .HasAnnotation("CaseSensitive", true); + + b.Property("Downloads"); + + b.Property("FileName") + .HasAnnotation("CaseSensitive", true); + + b.Property("IV") + .HasAnnotation("CaseSensitive", true); + + b.Property("Key") + .HasAnnotation("CaseSensitive", true); + + b.Property("KeySize"); + + b.Property("Takedown_TakedownId"); + + b.Property("Url") + .HasAnnotation("CaseSensitive", true); + + b.Property("UserId"); + + b.HasKey("UploadId"); + + b.HasIndex("Takedown_TakedownId"); + + b.HasIndex("UserId"); + + b.ToTable("Uploads"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.InviteCode", b => + { + b.Property("InviteCodeId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Active"); + + b.Property("ClaimedUserId"); + + b.Property("Code") + .HasAnnotation("CaseSensitive", true); + + b.Property("OwnerId"); + + b.HasKey("InviteCodeId"); + + b.HasIndex("ClaimedUserId") + .IsUnique() + .HasFilter("[ClaimedUserId] IS NOT NULL"); + + b.HasIndex("OwnerId"); + + b.ToTable("InviteCodes"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.LoginInfo", b => + { + b.Property("LoginInfoId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("LoginProvider"); + + b.Property("ProviderDisplayName"); + + b.Property("ProviderKey"); + + b.Property("UserId"); + + b.HasKey("LoginInfoId"); + + b.HasIndex("UserId"); + + b.ToTable("UserLogins"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Username"); + + b.HasKey("UserId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.Vault", b => + { + b.Property("VaultId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("DateCreated"); + + b.Property("DateEdited"); + + b.Property("Description"); + + b.Property("Title"); + + b.Property("Url"); + + b.Property("UserId"); + + b.Property("Views"); + + b.HasKey("VaultId"); + + b.HasIndex("UserId"); + + b.ToTable("Vaults"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.VaultItem", b => + { + b.Property("VaultItemId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("DateAdded"); + + b.Property("Description"); + + b.Property("Discriminator") + .IsRequired(); + + b.Property("Title"); + + b.Property("VaultId"); + + b.HasKey("VaultItemId"); + + b.HasIndex("VaultId"); + + b.ToTable("VaultItems"); + + b.HasDiscriminator("Discriminator").HasValue("VaultItem"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.PasteVaultItem", b => + { + b.HasBaseType("Teknik.Areas.Vault.Models.VaultItem"); + + b.Property("PasteId"); + + b.HasIndex("PasteId"); + + b.HasDiscriminator().HasValue("PasteVaultItem"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.UploadVaultItem", b => + { + b.HasBaseType("Teknik.Areas.Vault.Models.VaultItem"); + + b.Property("UploadId"); + + b.HasIndex("UploadId"); + + b.HasDiscriminator().HasValue("UploadVaultItem"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.Blog", b => + { + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPost", b => + { + b.HasOne("Teknik.Areas.Blog.Models.Blog", "Blog") + .WithMany("BlogPosts") + .HasForeignKey("BlogId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPostComment", b => + { + b.HasOne("Teknik.Areas.Blog.Models.BlogPost", "BlogPost") + .WithMany("Comments") + .HasForeignKey("BlogPostId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPostTag", b => + { + b.HasOne("Teknik.Areas.Blog.Models.BlogPost", "BlogPost") + .WithMany("Tags") + .HasForeignKey("BlogPostId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Teknik.Areas.Paste.Models.Paste", b => + { + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany("Pastes") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastComment", b => + { + b.HasOne("Teknik.Areas.Podcast.Models.Podcast", "Podcast") + .WithMany("Comments") + .HasForeignKey("PodcastId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastFile", b => + { + b.HasOne("Teknik.Areas.Podcast.Models.Podcast", "Podcast") + .WithMany("Files") + .HasForeignKey("PodcastId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastTag", b => + { + b.HasOne("Teknik.Areas.Podcast.Models.Podcast", "Podcast") + .WithMany("Tags") + .HasForeignKey("PodcastId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Teknik.Areas.Shortener.Models.ShortenedUrl", b => + { + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany("ShortenedUrls") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Teknik.Areas.Upload.Models.Upload", b => + { + b.HasOne("Teknik.Areas.Stats.Models.Takedown") + .WithMany("Attachments") + .HasForeignKey("Takedown_TakedownId"); + + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany("Uploads") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.InviteCode", b => + { + b.HasOne("Teknik.Areas.Users.Models.User", "ClaimedUser") + .WithOne("ClaimedInviteCode") + .HasForeignKey("Teknik.Areas.Users.Models.InviteCode", "ClaimedUserId"); + + b.HasOne("Teknik.Areas.Users.Models.User", "Owner") + .WithMany("OwnedInviteCodes") + .HasForeignKey("OwnerId"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.LoginInfo", b => + { + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany("Logins") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.User", b => + { + b.OwnsOne("Teknik.Areas.Users.Models.BlogSettings", "BlogSettings", b1 => + { + b1.Property("UserId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b1.Property("Description") + .HasColumnName("Description"); + + b1.Property("Title") + .HasColumnName("Title"); + + b1.HasKey("UserId"); + + b1.ToTable("Users"); + + b1.HasOne("Teknik.Areas.Users.Models.User") + .WithOne("BlogSettings") + .HasForeignKey("Teknik.Areas.Users.Models.BlogSettings", "UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + b.OwnsOne("Teknik.Areas.Users.Models.UploadSettings", "UploadSettings", b1 => + { + b1.Property("UserId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b1.Property("Encrypt") + .HasColumnName("Encrypt"); + + b1.HasKey("UserId"); + + b1.ToTable("Users"); + + b1.HasOne("Teknik.Areas.Users.Models.User") + .WithOne("UploadSettings") + .HasForeignKey("Teknik.Areas.Users.Models.UploadSettings", "UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + b.OwnsOne("Teknik.Areas.Users.Models.UserSettings", "UserSettings", b1 => + { + b1.Property("UserId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b1.Property("About") + .HasColumnName("About"); + + b1.Property("Quote") + .HasColumnName("Quote"); + + b1.Property("Website") + .HasColumnName("Website"); + + b1.HasKey("UserId"); + + b1.ToTable("Users"); + + b1.HasOne("Teknik.Areas.Users.Models.User") + .WithOne("UserSettings") + .HasForeignKey("Teknik.Areas.Users.Models.UserSettings", "UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.Vault", b => + { + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany("Vaults") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.VaultItem", b => + { + b.HasOne("Teknik.Areas.Vault.Models.Vault", "Vault") + .WithMany("VaultItems") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.PasteVaultItem", b => + { + b.HasOne("Teknik.Areas.Paste.Models.Paste", "Paste") + .WithMany("PasteVaultItems") + .HasForeignKey("PasteId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.UploadVaultItem", b => + { + b.HasOne("Teknik.Areas.Upload.Models.Upload", "Upload") + .WithMany("UploadVaultItems") + .HasForeignKey("UploadId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Teknik/Data/Migrations/20181021081119_RemovedVerificationTables.cs b/Teknik/Data/Migrations/20181021081119_RemovedVerificationTables.cs new file mode 100644 index 0000000..2126008 --- /dev/null +++ b/Teknik/Data/Migrations/20181021081119_RemovedVerificationTables.cs @@ -0,0 +1,73 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Teknik.Data.Migrations +{ + public partial class RemovedVerificationTables : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "RecoveryEmailVerifications"); + + migrationBuilder.DropTable( + name: "ResetPasswordVerifications"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "RecoveryEmailVerifications", + columns: table => new + { + RecoveryEmailVerificationId = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Code = table.Column(nullable: true), + DateCreated = table.Column(nullable: false), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RecoveryEmailVerifications", x => x.RecoveryEmailVerificationId); + table.ForeignKey( + name: "FK_RecoveryEmailVerifications_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "UserId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ResetPasswordVerifications", + columns: table => new + { + ResetPasswordVerificationId = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Code = table.Column(nullable: true), + DateCreated = table.Column(nullable: false), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ResetPasswordVerifications", x => x.ResetPasswordVerificationId); + table.ForeignKey( + name: "FK_ResetPasswordVerifications_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "UserId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_RecoveryEmailVerifications_UserId", + table: "RecoveryEmailVerifications", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_ResetPasswordVerifications_UserId", + table: "ResetPasswordVerifications", + column: "UserId"); + } + } +} diff --git a/Teknik/Data/Migrations/TeknikEntitiesModelSnapshot.cs b/Teknik/Data/Migrations/TeknikEntitiesModelSnapshot.cs new file mode 100644 index 0000000..9893fdd --- /dev/null +++ b/Teknik/Data/Migrations/TeknikEntitiesModelSnapshot.cs @@ -0,0 +1,731 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Teknik.Data; + +namespace Teknik.Data.Migrations +{ + [DbContext(typeof(TeknikEntities))] + partial class TeknikEntitiesModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.0-preview2-35157") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.Blog", b => + { + b.Property("BlogId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("UserId"); + + b.HasKey("BlogId"); + + b.HasIndex("UserId"); + + b.ToTable("Blogs"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPost", b => + { + b.Property("BlogPostId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Article"); + + b.Property("BlogId"); + + b.Property("DateEdited"); + + b.Property("DatePosted"); + + b.Property("DatePublished"); + + b.Property("Published"); + + b.Property("System"); + + b.Property("Title"); + + b.HasKey("BlogPostId"); + + b.HasIndex("BlogId"); + + b.ToTable("BlogPosts"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPostComment", b => + { + b.Property("BlogPostCommentId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Article"); + + b.Property("BlogPostId"); + + b.Property("DateEdited"); + + b.Property("DatePosted"); + + b.Property("UserId"); + + b.HasKey("BlogPostCommentId"); + + b.HasIndex("BlogPostId"); + + b.HasIndex("UserId"); + + b.ToTable("BlogPostComments"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPostTag", b => + { + b.Property("BlogPostTagId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("BlogPostId"); + + b.Property("Description"); + + b.Property("Name"); + + b.HasKey("BlogPostTagId"); + + b.HasIndex("BlogPostId"); + + b.ToTable("BlogPostTags"); + }); + + modelBuilder.Entity("Teknik.Areas.Contact.Models.Contact", b => + { + b.Property("ContactId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("DateAdded"); + + b.Property("Email"); + + b.Property("Message"); + + b.Property("Name"); + + b.Property("Subject"); + + b.HasKey("ContactId"); + + b.ToTable("Contact"); + }); + + modelBuilder.Entity("Teknik.Areas.Paste.Models.Paste", b => + { + b.Property("PasteId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("BlockSize"); + + b.Property("Content"); + + b.Property("DatePosted"); + + b.Property("ExpireDate"); + + b.Property("HashedPassword") + .HasAnnotation("CaseSensitive", true); + + b.Property("Hide"); + + b.Property("IV") + .HasAnnotation("CaseSensitive", true); + + b.Property("Key") + .HasAnnotation("CaseSensitive", true); + + b.Property("KeySize"); + + b.Property("MaxViews"); + + b.Property("Syntax"); + + b.Property("Title"); + + b.Property("Url") + .HasAnnotation("CaseSensitive", true); + + b.Property("UserId"); + + b.Property("Views"); + + b.HasKey("PasteId"); + + b.HasIndex("UserId"); + + b.ToTable("Pastes"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.Podcast", b => + { + b.Property("PodcastId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("DateEdited"); + + b.Property("DatePosted"); + + b.Property("DatePublished"); + + b.Property("Description"); + + b.Property("Episode"); + + b.Property("Published"); + + b.Property("Title"); + + b.HasKey("PodcastId"); + + b.ToTable("Podcasts"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastComment", b => + { + b.Property("PodcastCommentId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Article"); + + b.Property("DateEdited"); + + b.Property("DatePosted"); + + b.Property("PodcastId"); + + b.Property("UserId"); + + b.HasKey("PodcastCommentId"); + + b.HasIndex("PodcastId"); + + b.HasIndex("UserId"); + + b.ToTable("PodcastComments"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastFile", b => + { + b.Property("PodcastFileId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ContentLength"); + + b.Property("ContentType"); + + b.Property("FileName"); + + b.Property("Path"); + + b.Property("PodcastId"); + + b.Property("Size"); + + b.HasKey("PodcastFileId"); + + b.HasIndex("PodcastId"); + + b.ToTable("PodcastFiles"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastTag", b => + { + b.Property("PodcastTagId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description"); + + b.Property("Name"); + + b.Property("PodcastId"); + + b.HasKey("PodcastTagId"); + + b.HasIndex("PodcastId"); + + b.ToTable("PodcastTags"); + }); + + modelBuilder.Entity("Teknik.Areas.Shortener.Models.ShortenedUrl", b => + { + b.Property("ShortenedUrlId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("DateAdded"); + + b.Property("OriginalUrl"); + + b.Property("ShortUrl") + .HasAnnotation("CaseSensitive", true); + + b.Property("UserId"); + + b.Property("Views"); + + b.HasKey("ShortenedUrlId"); + + b.HasIndex("UserId"); + + b.ToTable("ShortenedUrls"); + }); + + modelBuilder.Entity("Teknik.Areas.Stats.Models.Takedown", b => + { + b.Property("TakedownId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ActionTaken"); + + b.Property("DateActionTaken"); + + b.Property("DateRequested"); + + b.Property("Reason"); + + b.Property("Requester"); + + b.Property("RequesterContact"); + + b.HasKey("TakedownId"); + + b.ToTable("Takedowns"); + }); + + modelBuilder.Entity("Teknik.Areas.Stats.Models.Transaction", b => + { + b.Property("TransactionId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Amount") + .HasColumnType("decimal(19, 5)"); + + b.Property("Currency"); + + b.Property("DateSent"); + + b.Property("Reason"); + + b.HasKey("TransactionId"); + + b.ToTable("Transactions"); + }); + + modelBuilder.Entity("Teknik.Areas.Upload.Models.Upload", b => + { + b.Property("UploadId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("BlockSize"); + + b.Property("ContentLength"); + + b.Property("ContentType"); + + b.Property("DateUploaded"); + + b.Property("DeleteKey") + .HasAnnotation("CaseSensitive", true); + + b.Property("Downloads"); + + b.Property("FileName") + .HasAnnotation("CaseSensitive", true); + + b.Property("IV") + .HasAnnotation("CaseSensitive", true); + + b.Property("Key") + .HasAnnotation("CaseSensitive", true); + + b.Property("KeySize"); + + b.Property("Takedown_TakedownId"); + + b.Property("Url") + .HasAnnotation("CaseSensitive", true); + + b.Property("UserId"); + + b.HasKey("UploadId"); + + b.HasIndex("Takedown_TakedownId"); + + b.HasIndex("UserId"); + + b.ToTable("Uploads"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.InviteCode", b => + { + b.Property("InviteCodeId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Active"); + + b.Property("ClaimedUserId"); + + b.Property("Code") + .HasAnnotation("CaseSensitive", true); + + b.Property("OwnerId"); + + b.HasKey("InviteCodeId"); + + b.HasIndex("ClaimedUserId") + .IsUnique() + .HasFilter("[ClaimedUserId] IS NOT NULL"); + + b.HasIndex("OwnerId"); + + b.ToTable("InviteCodes"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.LoginInfo", b => + { + b.Property("LoginInfoId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("LoginProvider"); + + b.Property("ProviderDisplayName"); + + b.Property("ProviderKey"); + + b.Property("UserId"); + + b.HasKey("LoginInfoId"); + + b.HasIndex("UserId"); + + b.ToTable("UserLogins"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Username"); + + b.HasKey("UserId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.Vault", b => + { + b.Property("VaultId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("DateCreated"); + + b.Property("DateEdited"); + + b.Property("Description"); + + b.Property("Title"); + + b.Property("Url"); + + b.Property("UserId"); + + b.Property("Views"); + + b.HasKey("VaultId"); + + b.HasIndex("UserId"); + + b.ToTable("Vaults"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.VaultItem", b => + { + b.Property("VaultItemId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("DateAdded"); + + b.Property("Description"); + + b.Property("Discriminator") + .IsRequired(); + + b.Property("Title"); + + b.Property("VaultId"); + + b.HasKey("VaultItemId"); + + b.HasIndex("VaultId"); + + b.ToTable("VaultItems"); + + b.HasDiscriminator("Discriminator").HasValue("VaultItem"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.PasteVaultItem", b => + { + b.HasBaseType("Teknik.Areas.Vault.Models.VaultItem"); + + b.Property("PasteId"); + + b.HasIndex("PasteId"); + + b.HasDiscriminator().HasValue("PasteVaultItem"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.UploadVaultItem", b => + { + b.HasBaseType("Teknik.Areas.Vault.Models.VaultItem"); + + b.Property("UploadId"); + + b.HasIndex("UploadId"); + + b.HasDiscriminator().HasValue("UploadVaultItem"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.Blog", b => + { + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPost", b => + { + b.HasOne("Teknik.Areas.Blog.Models.Blog", "Blog") + .WithMany("BlogPosts") + .HasForeignKey("BlogId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPostComment", b => + { + b.HasOne("Teknik.Areas.Blog.Models.BlogPost", "BlogPost") + .WithMany("Comments") + .HasForeignKey("BlogPostId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPostTag", b => + { + b.HasOne("Teknik.Areas.Blog.Models.BlogPost", "BlogPost") + .WithMany("Tags") + .HasForeignKey("BlogPostId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Teknik.Areas.Paste.Models.Paste", b => + { + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany("Pastes") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastComment", b => + { + b.HasOne("Teknik.Areas.Podcast.Models.Podcast", "Podcast") + .WithMany("Comments") + .HasForeignKey("PodcastId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastFile", b => + { + b.HasOne("Teknik.Areas.Podcast.Models.Podcast", "Podcast") + .WithMany("Files") + .HasForeignKey("PodcastId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastTag", b => + { + b.HasOne("Teknik.Areas.Podcast.Models.Podcast", "Podcast") + .WithMany("Tags") + .HasForeignKey("PodcastId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Teknik.Areas.Shortener.Models.ShortenedUrl", b => + { + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany("ShortenedUrls") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Teknik.Areas.Upload.Models.Upload", b => + { + b.HasOne("Teknik.Areas.Stats.Models.Takedown") + .WithMany("Attachments") + .HasForeignKey("Takedown_TakedownId"); + + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany("Uploads") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.InviteCode", b => + { + b.HasOne("Teknik.Areas.Users.Models.User", "ClaimedUser") + .WithOne("ClaimedInviteCode") + .HasForeignKey("Teknik.Areas.Users.Models.InviteCode", "ClaimedUserId"); + + b.HasOne("Teknik.Areas.Users.Models.User", "Owner") + .WithMany("OwnedInviteCodes") + .HasForeignKey("OwnerId"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.LoginInfo", b => + { + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany("Logins") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.User", b => + { + b.OwnsOne("Teknik.Areas.Users.Models.BlogSettings", "BlogSettings", b1 => + { + b1.Property("UserId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b1.Property("Description") + .HasColumnName("Description"); + + b1.Property("Title") + .HasColumnName("Title"); + + b1.HasKey("UserId"); + + b1.ToTable("Users"); + + b1.HasOne("Teknik.Areas.Users.Models.User") + .WithOne("BlogSettings") + .HasForeignKey("Teknik.Areas.Users.Models.BlogSettings", "UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + b.OwnsOne("Teknik.Areas.Users.Models.UploadSettings", "UploadSettings", b1 => + { + b1.Property("UserId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b1.Property("Encrypt") + .HasColumnName("Encrypt"); + + b1.HasKey("UserId"); + + b1.ToTable("Users"); + + b1.HasOne("Teknik.Areas.Users.Models.User") + .WithOne("UploadSettings") + .HasForeignKey("Teknik.Areas.Users.Models.UploadSettings", "UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + b.OwnsOne("Teknik.Areas.Users.Models.UserSettings", "UserSettings", b1 => + { + b1.Property("UserId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b1.Property("About") + .HasColumnName("About"); + + b1.Property("Quote") + .HasColumnName("Quote"); + + b1.Property("Website") + .HasColumnName("Website"); + + b1.HasKey("UserId"); + + b1.ToTable("Users"); + + b1.HasOne("Teknik.Areas.Users.Models.User") + .WithOne("UserSettings") + .HasForeignKey("Teknik.Areas.Users.Models.UserSettings", "UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.Vault", b => + { + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany("Vaults") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.VaultItem", b => + { + b.HasOne("Teknik.Areas.Vault.Models.Vault", "Vault") + .WithMany("VaultItems") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.PasteVaultItem", b => + { + b.HasOne("Teknik.Areas.Paste.Models.Paste", "Paste") + .WithMany("PasteVaultItems") + .HasForeignKey("PasteId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.UploadVaultItem", b => + { + b.HasOne("Teknik.Areas.Upload.Models.Upload", "Upload") + .WithMany("UploadVaultItems") + .HasForeignKey("UploadId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Teknik/Data/TeknikEntities.cs b/Teknik/Data/TeknikEntities.cs index ef8a36a..5dfecaa 100644 --- a/Teknik/Data/TeknikEntities.cs +++ b/Teknik/Data/TeknikEntities.cs @@ -20,20 +20,11 @@ namespace Teknik.Data // Users public DbSet Users { get; set; } public DbSet UserLogins { get; set; } - public DbSet UserRoles { get; set; } - public DbSet Roles { get; set; } - public DbSet TrustedDevices { get; set; } - public DbSet AuthTokens { get; set; } - public DbSet TransferTypes { get; set; } public DbSet InviteCodes { get; set; } // User Settings public DbSet UserSettings { get; set; } - public DbSet SecuritySettings { get; set; } public DbSet BlogSettings { get; set; } public DbSet UploadSettings { get; set; } - // Authentication and Sessions - public DbSet RecoveryEmailVerifications { get; set; } - public DbSet ResetPasswordVerifications { get; set; } // Blogs public DbSet Blogs { get; set; } public DbSet BlogPosts { get; set; } @@ -76,81 +67,19 @@ namespace Teknik.Data // User modelBuilder.Entity() .HasKey(u => u.UserId); - modelBuilder.Entity() - .HasOne(u => u.UserSettings).WithOne(u => u.User).HasForeignKey(u => u.UserId); - modelBuilder.Entity() - .HasOne(u => u.SecuritySettings).WithOne(u => u.User).HasForeignKey(u => u.UserId); - modelBuilder.Entity() - .HasOne(u => u.BlogSettings).WithOne(u => u.User).HasForeignKey(u => u.UserId); - modelBuilder.Entity() - .HasOne(u => u.UploadSettings).WithOne(u => u.User).HasForeignKey(u => u.UserId); + modelBuilder.Entity().OwnsOne(u => u.UserSettings); + modelBuilder.Entity().OwnsOne(u => u.BlogSettings); + modelBuilder.Entity().OwnsOne(u => u.UploadSettings); modelBuilder.Entity().HasMany(u => u.Uploads).WithOne(u => u.User); modelBuilder.Entity().HasMany(u => u.Pastes).WithOne(u => u.User); modelBuilder.Entity().HasMany(u => u.ShortenedUrls).WithOne(u => u.User); modelBuilder.Entity().HasMany(u => u.Vaults).WithOne(u => u.User); modelBuilder.Entity().HasMany(u => u.OwnedInviteCodes).WithOne(i => i.Owner); - modelBuilder.Entity().HasMany(u => u.Transfers).WithOne(i => i.User); modelBuilder.Entity().HasOne(u => u.ClaimedInviteCode).WithOne(i => i.ClaimedUser); modelBuilder.Entity().HasMany(u => u.Logins).WithOne(r => r.User); - modelBuilder.Entity().HasMany(u => u.UserRoles).WithOne(r => r.User); modelBuilder.Entity().HasOne(u => u.ClaimedInviteCode).WithOne(t => t.ClaimedUser); // Legacy??? modelBuilder.Entity().HasMany(u => u.OwnedInviteCodes).WithOne(t => t.Owner); // Legacy??? - // User Settings - modelBuilder.Entity() - .HasKey(u => u.UserId); - modelBuilder.Entity() - .HasOne(u => u.User).WithOne(u => u.UserSettings).HasForeignKey(u => u.UserId); - modelBuilder.Entity() - .HasOne(u => u.SecuritySettings).WithOne(u => u.UserSettings).HasForeignKey(u => u.UserId); - modelBuilder.Entity() - .HasOne(u => u.BlogSettings).WithOne(u => u.UserSettings).HasForeignKey(u => u.UserId); - modelBuilder.Entity() - .HasOne(u => u.UploadSettings).WithOne(u => u.UserSettings).HasForeignKey(u => u.UserId); - - // Security Settings - modelBuilder.Entity() - .HasKey(u => u.UserId); - modelBuilder.Entity() - .HasOne(u => u.User).WithOne(u => u.SecuritySettings).HasForeignKey(u => u.UserId); - modelBuilder.Entity() - .HasOne(u => u.UserSettings).WithOne(u => u.SecuritySettings).HasForeignKey(u => u.UserId); - modelBuilder.Entity() - .HasOne(u => u.BlogSettings).WithOne(u => u.SecuritySettings).HasForeignKey(u => u.UserId); - modelBuilder.Entity() - .HasOne(u => u.UploadSettings).WithOne(u => u.SecuritySettings).HasForeignKey(u => u.UserId); - - // Blog Settings - modelBuilder.Entity() - .HasKey(u => u.UserId); - modelBuilder.Entity() - .HasOne(u => u.User).WithOne(u => u.BlogSettings).HasForeignKey(u => u.UserId); - modelBuilder.Entity() - .HasOne(u => u.UserSettings).WithOne(u => u.BlogSettings).HasForeignKey(u => u.UserId); - modelBuilder.Entity() - .HasOne(u => u.SecuritySettings).WithOne(u => u.BlogSettings).HasForeignKey(u => u.UserId); - modelBuilder.Entity() - .HasOne(u => u.UploadSettings).WithOne(u => u.BlogSettings).HasForeignKey(u => u.UserId); - - // Upload Settings - modelBuilder.Entity() - .HasKey(u => u.UserId); - modelBuilder.Entity() - .HasOne(u => u.User).WithOne(u => u.UploadSettings).HasForeignKey(u => u.UserId); - modelBuilder.Entity() - .HasOne(u => u.UserSettings).WithOne(u => u.UploadSettings).HasForeignKey(u => u.UserId); - modelBuilder.Entity() - .HasOne(u => u.SecuritySettings).WithOne(u => u.UploadSettings).HasForeignKey(u => u.UserId); - modelBuilder.Entity() - .HasOne(u => u.BlogSettings).WithOne(u => u.UploadSettings).HasForeignKey(u => u.UserId); - - // UserRoles - modelBuilder.Entity().HasOne(r => r.User).WithMany(u => u.UserRoles); - modelBuilder.Entity().HasOne(r => r.Role).WithMany(r => r.UserRoles); - - // Roles - modelBuilder.Entity().HasMany(r => r.UserRoles).WithOne(r => r.Role); - // Invite Codes //modelBuilder.Entity().HasOne(t => t.ClaimedUser).WithOne(u => u.ClaimedInviteCode).HasPrincipalKey("ClaimedUserId").HasForeignKey("ClaimedUser_UserId"); // Legacy??? //modelBuilder.Entity().HasOne(t => t.Owner).WithMany(u => u.OwnedInviteCodes).HasPrincipalKey("ClaimedUserId").HasForeignKey("Owner_UserId"); // Legacy??? @@ -163,7 +92,6 @@ namespace Teknik.Data // Pastes modelBuilder.Entity().HasOne(u => u.User); - modelBuilder.Entity().HasMany(p => p.Transfers).WithOne(t => t.Paste); // Shortened URLs modelBuilder.Entity().HasOne(u => u.User); @@ -174,26 +102,15 @@ namespace Teknik.Data // Takedowns modelBuilder.Entity().HasMany(t => t.Attachments).WithOne().HasForeignKey("Takedown_TakedownId"); // Legacy??? - // Transfer Types - modelBuilder.Entity().HasOne(t => t.User).WithMany(u => u.Transfers); - modelBuilder.Entity().HasOne(t => t.Paste).WithMany(p => p.Transfers); - // Transactions modelBuilder.Entity().Property(t => t.Amount).HasColumnType("decimal(19, 5)"); // Users modelBuilder.Entity().ToTable("Users"); modelBuilder.Entity().ToTable("UserLogins"); - modelBuilder.Entity().ToTable("UserRoles"); - modelBuilder.Entity().ToTable("Roles"); - modelBuilder.Entity().ToTable("TrustedDevices"); - modelBuilder.Entity().ToTable("AuthTokens"); modelBuilder.Entity().ToTable("InviteCodes"); - modelBuilder.Entity().ToTable("RecoveryEmailVerifications"); - modelBuilder.Entity().ToTable("ResetPasswordVerifications"); // User Settings modelBuilder.Entity().ToTable("Users"); - modelBuilder.Entity().ToTable("Users"); modelBuilder.Entity().ToTable("Users"); modelBuilder.Entity().ToTable("Users"); // Blogs @@ -220,8 +137,6 @@ namespace Teknik.Data // Transparency modelBuilder.Entity().ToTable("Transactions"); modelBuilder.Entity().ToTable("Takedowns"); - // Transfer Types - modelBuilder.Entity().ToTable("TransferTypes"); // Custom Attributes foreach (var entityType in modelBuilder.Model.GetEntityTypes()) diff --git a/Teknik/Middleware/BlacklistMiddleware.cs b/Teknik/Middleware/BlacklistMiddleware.cs index 1f71888..0c1d21b 100644 --- a/Teknik/Middleware/BlacklistMiddleware.cs +++ b/Teknik/Middleware/BlacklistMiddleware.cs @@ -9,7 +9,6 @@ using System.Collections.Specialized; using System.IO; using System.Linq; using System.Threading.Tasks; -using Teknik.Areas.Error.Controllers; using Teknik.Configuration; namespace Teknik.Middleware diff --git a/Teknik/Middleware/IdentityServerUrlMiddleware.cs b/Teknik/Middleware/IdentityServerUrlMiddleware.cs new file mode 100644 index 0000000..295503c --- /dev/null +++ b/Teknik/Middleware/IdentityServerUrlMiddleware.cs @@ -0,0 +1,65 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using IdentityServer4.Extensions; +using Teknik.Configuration; +using Teknik.Utilities; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.Routing; + +namespace Teknik.Middleware +{ + public class IdentityServerUrlMiddleware + { + private readonly RequestDelegate _next; + private readonly IRouter _router; + + public IdentityServerUrlMiddleware(RequestDelegate next, IRouter router) + { + _next = next; + _router = router; + } + + public async Task Invoke(HttpContext httpContext, Config config) + { + RouteData routeData = new RouteData(); + routeData.Routers.Add(_router); + + var context = new ActionContext(httpContext, routeData, new Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptor()); + + UrlHelper urlHelper = new UrlHelper(context); + + string baseUrl = urlHelper.SubRouteUrl("auth", "Auth.IdentityServer"); + + string curSub = baseUrl.GetSubdomain(); + //if (!string.IsNullOrEmpty(curSub) && curSub != "dev") + + httpContext.SetIdentityServerOrigin(baseUrl); + httpContext.SetIdentityServerBasePath(httpContext.Request.PathBase.Value.TrimEnd('/')); + + await _next(httpContext); + } + } + + // Extension method used to add the middleware to the HTTP request pipeline. + public static class IdentityServerUrlMiddlewareExtensions + { + public static IApplicationBuilder UseIdentityServerUrl(this IApplicationBuilder builder, Config config) + { + var routes = new RouteBuilder(builder) + { + DefaultHandler = builder.ApplicationServices.GetRequiredService(), + }; + routes.BuildRoutes(config); + + return builder.UseMiddleware(routes.Build()); + } + } +} diff --git a/Teknik/Models/TransferTypes.cs b/Teknik/Models/TransferTypes.cs deleted file mode 100644 index c06a0fe..0000000 --- a/Teknik/Models/TransferTypes.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Web; -using Teknik.Areas.Users.Models; -using Teknik.Areas.Paste.Models; - -namespace Teknik.Models -{ - public enum TransferTypes - { - Sha256Password = 0, - CaseSensitivePassword = 1, - ASCIIPassword = 2 - } - - public class TransferType - { - public int TransferTypeId { get; set; } - - public TransferTypes Type { get; set; } - - public virtual int? UserId { get; set; } - - public virtual User User { get; set; } - - public virtual int? PasteId { get; set; } - - public virtual Paste Paste { get; set; } - } -} \ No newline at end of file diff --git a/Teknik/Properties/launchSettings.json b/Teknik/Properties/launchSettings.json index a9af57f..8f26730 100644 --- a/Teknik/Properties/launchSettings.json +++ b/Teknik/Properties/launchSettings.json @@ -21,7 +21,7 @@ "launchBrowser": true, "launchUrl": "?sub=www", "environmentVariables": { - "ASPNETCORE_URLS": "https://localhost:5001;http://localhost:5000", + "ASPNETCORE_URLS": "http://localhost:5050", "ASPNETCORE_ENVIRONMENT": "Development" } }, @@ -30,7 +30,7 @@ "launchBrowser": true, "launchUrl": "?sub=www", "environmentVariables": { - "ASPNETCORE_URLS": "https://localhost:5001;http://localhost:5000", + "ASPNETCORE_URLS": "http://localhost:5050", "ASPNETCORE_ENVIRONMENT": "Production" } } diff --git a/Teknik/Routes.cs b/Teknik/Routes.cs index 379d8e6..303189a 100644 --- a/Teknik/Routes.cs +++ b/Teknik/Routes.cs @@ -15,7 +15,6 @@ namespace Teknik routes.BuildDefaultRoutes(config); routes.BuildAboutRoutes(config); routes.BuildAbuseRoutes(config); - routes.BuildAccountsRoutes(config); routes.BuildAdminRoutes(config); routes.BuildAPIRoutes(config); routes.BuildBlogRoutes(config); @@ -92,38 +91,6 @@ namespace Teknik ); } - public static void BuildAccountsRoutes(this IRouteBuilder routes, Config config) - { - routes.MapSubdomainRoute( - name: "Accounts.Login", - domains: new List() { config.Host }, - subDomains: new List() { "accounts" }, - template: "Login", - defaults: new { area = "Accounts", controller = "Accounts", action = "Login" } - ); - routes.MapSubdomainRoute( - name: "Accounts.Logout", - domains: new List() { config.Host }, - subDomains: new List() { "accounts" }, - template: "Logout", - defaults: new { area = "Accounts", controller = "Accounts", action = "Logout" } - ); - routes.MapSubdomainRoute( - name: "Accounts.CheckAuthenticatorCode", - domains: new List() { config.Host }, - subDomains: new List() { "accounts" }, - template: "CheckAuthCode", - defaults: new { area = "Accounts", controller = "Accounts", action = "ConfirmTwoFactorAuth" } - ); - routes.MapSubdomainRoute( - name: "Accounts.Action", - domains: new List() { config.Host }, - subDomains: new List() { "accounts" }, - template: "Action/{action}", - defaults: new { area = "Accounts", controller = "Accounts", action = "Index" } - ); - } - public static void BuildAdminRoutes(this IRouteBuilder routes, Config config) { routes.MapSubdomainRoute( @@ -166,32 +133,32 @@ namespace Teknik public static void BuildAPIRoutes(this IRouteBuilder routes, Config config) { routes.MapSubdomainRoute( - name: "API.v1.Index", + name: "API.v1.Claims", domains: new List() { config.Host }, subDomains: new List() { "api" }, - template: "v1", - defaults: new { area = "API", controller = "APIv1", action = "Index" } + template: "v1/Claims", + defaults: new { area = "API", controller = "AccountAPIv1", action = "GetClaims" } ); routes.MapSubdomainRoute( name: "API.v1.Upload", domains: new List() { config.Host }, subDomains: new List() { "api" }, template: "v1/Upload", - defaults: new { area = "API", controller = "APIv1", action = "Upload" } + defaults: new { area = "API", controller = "UploadAPIv1", action = "Upload" } ); routes.MapSubdomainRoute( name: "API.v1.Paste", domains: new List() { config.Host }, subDomains: new List() { "api" }, template: "v1/Paste", - defaults: new { area = "API", controller = "APIv1", action = "Paste" } + defaults: new { area = "API", controller = "PasteAPIv1", action = "Paste" } ); routes.MapSubdomainRoute( name: "API.v1.Shorten", domains: new List() { config.Host }, subDomains: new List() { "api" }, template: "v1/Shorten", - defaults: new { area = "API", controller = "APIv1", action = "Shorten" } + defaults: new { area = "API", controller = "ShortenAPIv1", action = "Shorten" } ); routes.MapSubdomainRoute( @@ -650,6 +617,20 @@ namespace Teknik template: "Register", defaults: new { area = "User", controller = "User", action = "Register" } ); + routes.MapSubdomainRoute( + name: "User.Login", + domains: new List() { config.Host }, + subDomains: new List() { "user" }, + template: "Login", + defaults: new { area = "User", controller = "User", action = "Login" } + ); + routes.MapSubdomainRoute( + name: "User.Logout", + domains: new List() { config.Host }, + subDomains: new List() { "user" }, + template: "Logout", + defaults: new { area = "User", controller = "User", action = "Logout" } + ); routes.MapSubdomainRoute( name: "User.Settings", domains: new List() { config.Host }, @@ -657,6 +638,13 @@ namespace Teknik template: "Settings", defaults: new { area = "User", controller = "User", action = "Settings" } ); + routes.MapSubdomainRoute( + name: "User.AccountSettings", + domains: new List() { config.Host }, + subDomains: new List() { "user" }, + template: "Settings/Account", + defaults: new { area = "User", controller = "User", action = "AccountSettings" } + ); routes.MapSubdomainRoute( name: "User.SecuritySettings", domains: new List() { config.Host }, @@ -671,6 +659,13 @@ namespace Teknik template: "Settings/Profile", defaults: new { area = "User", controller = "User", action = "ProfileSettings" } ); + routes.MapSubdomainRoute( + name: "User.AccessTokenSettings", + domains: new List() { config.Host }, + subDomains: new List() { "user" }, + template: "Settings/AccessTokens", + defaults: new { area = "User", controller = "User", action = "AccessTokenSettings" } + ); routes.MapSubdomainRoute( name: "User.InviteSettings", domains: new List() { config.Host }, @@ -696,21 +691,21 @@ namespace Teknik name: "User.ResetPassword", domains: new List() { config.Host }, subDomains: new List() { "user" }, - template: "Reset/{username?}", + template: "ResetPassword/{username?}", defaults: new { area = "User", controller = "User", action = "ResetPassword" } ); routes.MapSubdomainRoute( name: "User.VerifyResetPassword", domains: new List() { config.Host }, subDomains: new List() { "user" }, - template: "Reset/{username}/{code}", + template: "SetPassword/{username}", defaults: new { area = "User", controller = "User", action = "VerifyResetPassword" } ); routes.MapSubdomainRoute( name: "User.VerifyRecoveryEmail", domains: new List() { config.Host }, subDomains: new List() { "user" }, - template: "VerifyEmail/{code}", + template: "VerifyEmail/{username}", defaults: new { area = "User", controller = "User", action = "VerifyRecoveryEmail" } ); routes.MapSubdomainRoute( diff --git a/Teknik/Scripts/User/APIClientSettings.js b/Teknik/Scripts/User/APIClientSettings.js new file mode 100644 index 0000000..76a5908 --- /dev/null +++ b/Teknik/Scripts/User/APIClientSettings.js @@ -0,0 +1,135 @@ +$(document).ready(function () { + $('#create_client').click(function () { + bootbox.prompt("Specify a name for this Client", function (result) { + if (result) { + $.ajax({ + type: "POST", + url: createClientURL, + data: AddAntiForgeryToken({ name: result }), + success: function (response) { + if (response.result) { + var dialog = bootbox.dialog({ + closeButton: false, + buttons: { + close: { + label: 'Close', + className: 'btn-primary', + callback: function () { + if ($('#noAuthTokens')) { + $('#noAuthTokens').remove(); + } + var item = $(response.result.html); + + var deleteBtn = item.find('.deleteAuthToken'); + var editBtn = item.find('.editAuthToken'); + + deleteBtn.click(function () { + var authTokenId = $(this).attr("data-authid"); + deleteAuthToken(authTokenId); + }); + + editBtn.click(function () { + var authTokenId = $(this).attr("data-authid"); + editAuthToken(authTokenId); + }); + + $('#authTokenList').append(item); + } + } + }, + title: "Authentication Token", + message: '', + }); + + dialog.init(function () { + dialog.find('#authToken').click(function () { + $(this).select(); + }); + }); + } + else { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); + } + } + }); + } + }); + }); + + $('#revoke_all_clients').click(function () { + bootbox.confirm("Are you sure you want to revoke all your clients?

    This is irreversable and all applications using these clients will stop working.", function (result) { + if (result) { + $.ajax({ + type: "POST", + url: revokeAllClientsURL, + data: AddAntiForgeryToken({}), + success: function (response) { + if (response.result) { + $('#authTokenList').html('
  • No Authentication Tokens
  • '); + } + else { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); + } + } + }); + } + }); + }); + + $(".deleteAuthToken").click(function () { + var authTokenId = $(this).attr("data-authid"); + deleteAuthToken(authTokenId); + }); + + $(".editAuthToken").click(function () { + var authTokenId = $(this).attr("data-authid"); + editAuthToken(authTokenId); + }); +}); + +function editAuthToken(authTokenId) { + bootbox.prompt("Specify a new name for this Auth Token", function (result) { + if (result) { + $.ajax({ + type: "POST", + url: editTokenNameURL, + data: AddAntiForgeryToken({ tokenId: authTokenId, name: result }), + success: function (response) { + if (response.result) { + $('#authTokenName_' + authTokenId).html(response.result.name); + } + else { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); + } + } + }); + } + }); +} + +function deleteAuthToken(authTokenId) { + bootbox.confirm("Are you sure you want to revoke this auth token?

    This is irreversable and all applications using this token will stop working.", function (result) { + if (result) { + $.ajax({ + type: "POST", + url: deleteTokenURL, + data: AddAntiForgeryToken({ tokenId: authTokenId }), + success: function (response) { + if (response.result) { + $('#authToken_' + authTokenId).remove(); + if ($('#authTokenList li').length <= 0) { + $('#authTokenList').html('
  • No Authentication Tokens
  • '); + } + } + else { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); + } + } + }); + } + }); +} diff --git a/Teknik/Scripts/User/AccountSettings.js b/Teknik/Scripts/User/AccountSettings.js new file mode 100644 index 0000000..a340313 --- /dev/null +++ b/Teknik/Scripts/User/AccountSettings.js @@ -0,0 +1,62 @@ +$(document).ready(function () { + $("#change_password_submit").click(function () { + // Start Updating Animation + disableButton('#change_password_submit', 'Saving...'); + + current_password = $("#update_password_current").val(); + password = $("#update_password").val(); + password_confirm = $("#update_password_confirm").val(); + $.ajax({ + type: "POST", + url: changePasswordUrl, + data: AddAntiForgeryToken({ + CurrentPassword: current_password, + NewPassword: password, + NewPasswordConfirm: password_confirm + }), + success: function (response) { + enableButton('#change_password_submit', 'Change Password'); + if (response.result) { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    Password has successfully been changed.
    '); + } + else { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); + } + }, + error: function (response) { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    ' + parseErrorMessage(response.responseText) + '
    '); + } + }).always(function () { + enableButton('#change_password_submit', 'Change Password'); + }); + return false; + }); + + $('#delete_account').click(function () { + bootbox.confirm("Are you sure you want to delete your account?", function (result) { + if (result) { + $.ajax({ + type: "POST", + url: deleteUserURL, + data: AddAntiForgeryToken({}), + success: function (response) { + if (response.result) { + window.location.replace(homeUrl); + } + else { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); + } + }, + error: function (response) { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    ' + parseErrorMessage(response.responseText) + '
    '); + } + }); + } + }); + }); +}); \ No newline at end of file diff --git a/Teknik/Scripts/User/BlogSettings.js b/Teknik/Scripts/User/BlogSettings.js index 8ef4cc2..b897491 100644 --- a/Teknik/Scripts/User/BlogSettings.js +++ b/Teknik/Scripts/User/BlogSettings.js @@ -22,7 +22,13 @@ $(document).ready(function () { $("#top_msg").css('display', 'inline', 'important'); $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); } + }, + error: function (response) { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    ' + parseErrorMessage(response.responseText) + '
    '); } + }).always(function () { + enableButton('#update_submit', 'Save'); }); return false; }); diff --git a/Teknik/Scripts/User/CheckAuthCode.js b/Teknik/Scripts/User/CheckAuthCode.js index 5c05ad0..041fbec 100644 --- a/Teknik/Scripts/User/CheckAuthCode.js +++ b/Teknik/Scripts/User/CheckAuthCode.js @@ -33,6 +33,10 @@ $(document).ready(function () { $("#authCheckStatus").css('display', 'inline', 'important'); $("#authCheckStatus").html('
    ' + parseErrorMessage(response) + '
    '); } + }, + error: function (response) { + $("#authCheckStatus").css('display', 'inline', 'important'); + $("#authCheckStatus").html('
    ' + parseErrorMessage(response.responseText) + '
    '); } }); return false; diff --git a/Teknik/Scripts/User/InviteSettings.js b/Teknik/Scripts/User/InviteSettings.js index 3ed0e3a..8d81312 100644 --- a/Teknik/Scripts/User/InviteSettings.js +++ b/Teknik/Scripts/User/InviteSettings.js @@ -42,6 +42,10 @@ function createExternalLink(inviteCodeId) { $("#top_msg").css('display', 'inline', 'important'); $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); } + }, + error: function (response) { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    ' + parseErrorMessage(response.responseText) + '
    '); } }); } diff --git a/Teknik/Scripts/User/Profile.js b/Teknik/Scripts/User/Profile.js index 1d6a472..9029a66 100644 --- a/Teknik/Scripts/User/Profile.js +++ b/Teknik/Scripts/User/Profile.js @@ -26,6 +26,10 @@ $(document).ready(function () { $("#top_msg").css('display', 'inline', 'important'); $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); } + }, + error: function (response) { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    ' + parseErrorMessage(response.responseText) + '
    '); } }); } @@ -53,6 +57,10 @@ $(document).ready(function () { $("#top_msg").css('display', 'inline', 'important'); $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); } + }, + error: function (response) { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    ' + parseErrorMessage(response.responseText) + '
    '); } }); } diff --git a/Teknik/Scripts/User/ProfileSettings.js b/Teknik/Scripts/User/ProfileSettings.js index 2c208cc..3404a97 100644 --- a/Teknik/Scripts/User/ProfileSettings.js +++ b/Teknik/Scripts/User/ProfileSettings.js @@ -15,7 +15,6 @@ $(document).ready(function () { About: about }), success: function (response) { - enableButton('#update_submit', 'Save'); if (response.result) { $("#top_msg").css('display', 'inline', 'important'); $("#top_msg").html('
    Settings Saved!
    '); @@ -24,7 +23,13 @@ $(document).ready(function () { $("#top_msg").css('display', 'inline', 'important'); $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); } + }, + error: function (response) { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    ' + parseErrorMessage(response.responseText) + '
    '); } + }).always(function () { + enableButton('#update_submit', 'Save'); }); return false; }); diff --git a/Teknik/Scripts/User/ResetPass.js b/Teknik/Scripts/User/ResetPass.js index 4cb58fe..6a23992 100644 --- a/Teknik/Scripts/User/ResetPass.js +++ b/Teknik/Scripts/User/ResetPass.js @@ -1,5 +1,6 @@ $(document).ready(function () { $("#reset_pass_send_submit").click(function () { + disableButton('#reset_pass_send_submit', 'Generating Link...'); var form = $('#reset_pass_send'); username = $("#reset_username").val(); $.ajax({ @@ -11,18 +12,25 @@ $(document).ready(function () { success: function (response) { if (response.result) { $("#top_msg").css('display', 'inline', 'important'); - $("#top_msg").html('
    The Password Reset Link has been sent to your recovery email.
    '); + $("#top_msg").html('
    A Password Reset Link has been sent to your recovery email.
    '); } else { $("#top_msg").css('display', 'inline', 'important'); $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); } + }, + error: function (response) { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    ' + parseErrorMessage(response.responseText) + '
    '); } + }).always(function () { + enableButton('#reset_pass_send_submit', 'Send Reset Link'); }); return false; }); $("#setNewPass_submit").click(function () { + disableButton('#setNewPass_submit', 'Resetting...'); var form = $('#setNewPass'); password = $("#setNewPass_Password").val(); confirmPassword = $("#setNewPass_ConfirmPassword").val(); @@ -42,7 +50,13 @@ $(document).ready(function () { $("#top_msg").css('display', 'inline', 'important'); $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); } + }, + error: function (response) { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    ' + parseErrorMessage(response.responseText) + '
    '); } + }).always(function () { + enableButton('#setNewPass_submit', 'Reset Password'); }); return false; }); diff --git a/Teknik/Scripts/User/SecuritySettings.js b/Teknik/Scripts/User/SecuritySettings.js index b19fd51..08520d2 100644 --- a/Teknik/Scripts/User/SecuritySettings.js +++ b/Teknik/Scripts/User/SecuritySettings.js @@ -1,7 +1,4 @@ $(document).ready(function () { - $("[name='update_security_two_factor']").bootstrapSwitch(); - $("[name='update_security_allow_trusted']").bootstrapSwitch(); - $('#ResendVerification').click(function () { $.ajax({ type: "POST", @@ -16,6 +13,120 @@ $(document).ready(function () { $("#top_msg").css('display', 'inline', 'important'); $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); } + }, + error: function (response) { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    ' + parseErrorMessage(response.responseText) + '
    '); + } + }); + }); + + $('#disable_2fa_button').click(function () { + bootbox.confirm({ + message: "Are you sure you want to disable Two-Factor Authentication? This will also invalidate all current recovery codes.", + buttons: { + confirm: { + className: 'btn-danger', + label: 'Disable' + } + }, + callback: function (result) { + if (result) { + disableButton('#disable_2fa_button', 'Disabling...'); + $.ajax({ + type: "POST", + url: disable2FAURL, + data: AddAntiForgeryToken({}), + success: function (response) { + if (response.result) { + window.location.reload(true); + } + else { + enableButton('#disable_2fa_button', 'Disable'); + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); + } + }, + error: function (response) { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    ' + parseErrorMessage(response.responseText) + '
    '); + } + }).always(function () { + enableButton('#disable_2fa_button', 'Disable'); + }); + } + } + }); + }); + + $('#enable_2fa_button').click(function () { + disableButton('#enable_2fa_button', 'Generating Key...'); + $.ajax({ + type: "POST", + url: generate2FAURL, + data: AddAntiForgeryToken({}), + success: function (response) { + if (response.result) { + $('#authQRCode').attr('src', response.qrUrl); + $('#authSetupSecretKey').html(response.key); + + $('#authenticatorSetup').modal('show'); + } + else { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); + } + }, + error: function (response) { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    ' + parseErrorMessage(response.responseText) + '
    '); + } + }).always(function () { + enableButton('#enable_2fa_button', 'Enable'); + }); + }); + + $('#resetRecoveryCodes').click(function () { + bootbox.confirm({ + message: "Are you sure you want to reset your recovery codes? This will invalidate all current recovery codes.", + buttons: { + confirm: { + className: 'btn-danger', + label: 'Reset' + } + }, + callback: function (result) { + if (result) { + $.ajax({ + type: "POST", + url: resetRecoveryCodesURL, + data: AddAntiForgeryToken({}), + success: function (response) { + if (response.result) { + var dialog = bootbox.dialog({ + closeButton: false, + buttons: { + close: { + label: 'Close', + className: 'btn-primary' + } + }, + title: "Recovery Codes", + message: '
    Make sure to copy your recovery codes. You won\'t be able to see them again!
    ' + response.recoveryCodes.join('
    ') + '
    ' + }); + } + else { + enableButton('#disable_2fa_button', 'Disable'); + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); + } + }, + error: function (response) { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    ' + parseErrorMessage(response.responseText) + '
    '); + } + }); + } } }); }); @@ -27,10 +138,13 @@ $(document).ready(function () { $('#authenticatorSetup').on('hide.bs.modal', function (e) { $("#authSetupStatus").css('display', 'none', 'important'); $("#authSetupStatus").html(''); + $('#authQRCode').attr('src', ''); + $('#authSetupSecretKey').html(''); $('#auth_setup_code').val(''); }); $('#auth_setup_verify').click(function () { + disableButton('#auth_setup_verify', 'Verifying...'); setCode = $("#auth_setup_code").val(); $.ajax({ type: "POST", @@ -40,112 +154,34 @@ $(document).ready(function () { }), success: function (response) { if (response.result) { - $("#authSetupStatus").css('display', 'inline', 'important'); - $("#authSetupStatus").html('
    Success!
    '); + $('#authenticatorSetup').modal('hide'); + var dialog = bootbox.dialog({ + closeButton: false, + buttons: { + close: { + label: 'Close', + className: 'btn-primary', + callback: function () { + window.location.reload(true); + } + } + }, + title: "Recovery Codes", + message: '
    Make sure to copy your recovery codes. You won\'t be able to see them again!
    ' + response.recoveryCodes.join('
    ') + '
    ' + }); } else { $("#authSetupStatus").css('display', 'inline', 'important'); $("#authSetupStatus").html('
    ' + parseErrorMessage(response) + '
    '); } + }, + error: function (response) { + $("#authSetupStatus").css('display', 'inline', 'important'); + $("#authSetupStatus").html('
    ' + parseErrorMessage(response.responseText) + '
    '); } - }); - }); - - $('#ClearDevices').click(function () { - $.ajax({ - type: "POST", - url: clearTrustedDevicesURL, - data: AddAntiForgeryToken({}), - success: function (response) { - if (response.result) { - $('#ClearDevices').html('Clear Trusted Devices (0)'); - $("#top_msg").css('display', 'inline', 'important'); - $("#top_msg").html('
    Successfully Cleared Trusted Devices
    '); - } - else { - $("#top_msg").css('display', 'inline', 'important'); - $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); - } - } - }); - }); - - $('#generate_token').click(function () { - bootbox.prompt("Specify a name for this Auth Token", function (result) { - if (result) { - $.ajax({ - type: "POST", - url: generateTokenURL, - data: AddAntiForgeryToken({ name: result }), - success: function (response) { - if (response.result) { - var dialog = bootbox.dialog({ - closeButton: false, - buttons: { - close: { - label: 'Close', - className: 'btn-primary', - callback: function () { - if ($('#noAuthTokens')) { - $('#noAuthTokens').remove(); - } - var item = $(response.result.html); - - var deleteBtn = item.find('.deleteAuthToken'); - var editBtn = item.find('.editAuthToken'); - - deleteBtn.click(function () { - var authTokenId = $(this).attr("data-authid"); - deleteAuthToken(authTokenId); - }); - - editBtn.click(function () { - var authTokenId = $(this).attr("data-authid"); - editAuthToken(authTokenId); - }); - - $('#authTokenList').append(item); - } - } - }, - title: "Authentication Token", - message: '', - }); - - dialog.init(function () { - dialog.find('#authToken').click(function () { - $(this).select(); - }); - }); - } - else { - $("#top_msg").css('display', 'inline', 'important'); - $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); - } - } - }); - } - }); - }); - - $('#revoke_all_tokens').click(function () { - bootbox.confirm("Are you sure you want to revoke all your auth tokens?

    This is irreversable and all applications using a token will stop working.", function (result) { - if (result) { - $.ajax({ - type: "POST", - url: revokeAllTokensURL, - data: AddAntiForgeryToken({}), - success: function (response) { - if (response.result) { - $('#authTokenList').html('
  • No Authentication Tokens
  • '); - } - else { - $("#top_msg").css('display', 'inline', 'important'); - $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); - } - } - }); - } + }).always(function () { + enableButton('#auth_setup_verify', 'Verify'); + $('#auth_setup_code').val(''); }); }); @@ -153,27 +189,16 @@ $(document).ready(function () { // Start Updating Animation disableButton('#update_submit', 'Saving...'); - current_password = $("#update_password_current").val(); - password = $("#update_password").val(); - password_confirm = $("#update_password_confirm").val(); update_pgp_public_key = $("#update_pgp_public_key").val(); - update_security_allow_trusted = $("#update_security_allow_trusted").is(":checked"); - update_security_two_factor = $("#update_security_two_factor").is(":checked"); recovery = $("#update_recovery_email").val(); $.ajax({ type: "POST", url: editURL, data: AddAntiForgeryToken({ - CurrentPassword: current_password, - NewPassword: password, - NewPasswordConfirm: password_confirm, PgpPublicKey: update_pgp_public_key, - AllowTrustedDevices: update_security_allow_trusted, - TwoFactorEnabled: update_security_two_factor, RecoveryEmail: recovery }), success: function (response) { - enableButton('#update_submit', 'Save'); if (response.result) { if (response.result.checkAuth) { @@ -192,111 +217,14 @@ $(document).ready(function () { $("#top_msg").css('display', 'inline', 'important'); $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); } + }, + error: function (response) { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    ' + parseErrorMessage(response.responseText) + '
    '); } + }).always(function () { + enableButton('#update_submit', 'Save'); }); return false; }); - - $("#reset_pass_send_submit").click(function () { - var form = $('#reset_pass_send'); - username = $("#reset_username").val(); - $.ajax({ - type: "POST", - url: form.attr('action'), - data: AddAntiForgeryToken({ - username: username - }), - success: function (response) { - if (response.result) { - $("#top_msg").css('display', 'inline', 'important'); - $("#top_msg").html('
    The Password Reset Link has been sent to your recovery email.
    '); - } - else { - $("#top_msg").css('display', 'inline', 'important'); - $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); - } - } - }); - return false; - }); - - $("#setNewPass_submit").click(function () { - var form = $('#setNewPass'); - password = $("#setNewPass_Password").val(); - confirmPassword = $("#setNewPass_ConfirmPassword").val(); - $.ajax({ - type: "POST", - url: form.attr('action'), - data: AddAntiForgeryToken({ - Password: password, - PasswordConfirm: confirmPassword - }), - success: function (response) { - if (response.result) { - $("#top_msg").css('display', 'inline', 'important'); - $("#top_msg").html('
    Password has successfully been reset.
    '); - } - else { - $("#top_msg").css('display', 'inline', 'important'); - $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); - } - } - }); - return false; - }); - - $(".deleteAuthToken").click(function() { - var authTokenId = $(this).attr("data-authid"); - deleteAuthToken(authTokenId); - }); - - $(".editAuthToken").click(function () { - var authTokenId = $(this).attr("data-authid"); - editAuthToken(authTokenId); - }); -}); - -function editAuthToken(authTokenId) { - bootbox.prompt("Specify a new name for this Auth Token", function (result) { - if (result) { - $.ajax({ - type: "POST", - url: editTokenNameURL, - data: AddAntiForgeryToken({ tokenId: authTokenId, name: result }), - success: function (response) { - if (response.result) { - $('#authTokenName_' + authTokenId).html(response.result.name); - } - else { - $("#top_msg").css('display', 'inline', 'important'); - $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); - } - } - }); - } - }); -} - -function deleteAuthToken(authTokenId) { - bootbox.confirm("Are you sure you want to revoke this auth token?

    This is irreversable and all applications using this token will stop working.", function (result) { - if (result) { - $.ajax({ - type: "POST", - url: deleteTokenURL, - data: AddAntiForgeryToken({ tokenId: authTokenId }), - success: function (response) { - if (response.result) { - $('#authToken_' + authTokenId).remove(); - if ($('#authTokenList li').length <= 0) { - $('#authTokenList').html('
  • No Authentication Tokens
  • '); - } - } - else { - $("#top_msg").css('display', 'inline', 'important'); - $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); - } - } - }); - } - }); -} +}); \ No newline at end of file diff --git a/Teknik/Scripts/User/Settings.js b/Teknik/Scripts/User/Settings.js deleted file mode 100644 index 67f6b26..0000000 --- a/Teknik/Scripts/User/Settings.js +++ /dev/null @@ -1,22 +0,0 @@ -$(document).ready(function () { - $('#delete_account').click(function () { - bootbox.confirm("Are you sure you want to delete your account?", function (result) { - if (result) { - $.ajax({ - type: "POST", - url: deleteUserURL, - data: AddAntiForgeryToken({}), - success: function (response) { - if (response.result) { - window.location.replace(homeUrl); - } - else { - $("#top_msg").css('display', 'inline', 'important'); - $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); - } - } - }); - } - }); - }); -}); diff --git a/Teknik/Scripts/User/UploadSettings.js b/Teknik/Scripts/User/UploadSettings.js index abc112a..2ec7bea 100644 --- a/Teknik/Scripts/User/UploadSettings.js +++ b/Teknik/Scripts/User/UploadSettings.js @@ -13,7 +13,6 @@ $(document).ready(function () { Encrypt: upload_encrypt }), success: function (response) { - enableButton('#update_submit', 'Save'); if (response.result) { $("#top_msg").css('display', 'inline', 'important'); $("#top_msg").html('
    Settings Saved!
    '); @@ -22,7 +21,13 @@ $(document).ready(function () { $("#top_msg").css('display', 'inline', 'important'); $("#top_msg").html('
    ' + parseErrorMessage(response) + '
    '); } + }, + error: function (response) { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    ' + parseErrorMessage(response.responseText) + '
    '); } + }).always(function () { + enableButton('#update_submit', 'Save'); }); return false; }); diff --git a/Teknik/Scripts/common.js b/Teknik/Scripts/common.js index 84b4a8b..ed926cb 100644 --- a/Teknik/Scripts/common.js +++ b/Teknik/Scripts/common.js @@ -4,51 +4,6 @@ $(document).ready(function () { // Opt-in for tooltips $('[data-toggle="tooltip"]').tooltip(); - $('#loginButton').removeClass('hide'); - - $('#loginModal').on('shown.bs.modal', function (e) { - $("#loginStatus").css('display', 'none', 'important'); - $("#loginStatus").html(''); - $('#loginUsername').focus(); - }); - - $("#loginSubmit").click(function () { - // Reset login status messages - $("#loginStatus").css('display', 'none', 'important'); - $("#loginStatus").html(''); - - // Disable the login button - disableButton('#loginSubmit', 'Signing In...'); - - var form = $('#loginForm'); - $.ajax({ - type: "POST", - url: form.attr('action'), - data: form.serialize(), - headers: {'X-Requested-With': 'XMLHttpRequest'}, - xhrFields: { - withCredentials: true - }, - success: function (html) { - if (html.result) { - window.location = html.result; - } - else { - $("#loginStatus").css('display', 'inline', 'important'); - $("#loginStatus").html('
    ' + parseErrorMessage(html) + '
    '); - } - - // Re-enable the login button - enableButton('#loginSubmit', 'Sign In'); - }, - error: function (response) { - $("#loginStatus").css('display', 'inline', 'important'); - $("#loginStatus").html('
    ' + parseErrorMessage(response.responseJSON) + '
    '); - } - }); - return false; - }); - $('#registerButton').removeClass('hide'); $('#registerModal').on('shown.bs.modal', function (e) { @@ -70,7 +25,7 @@ $(document).ready(function () { type: "POST", url: form.attr('action'), data: form.serialize(), - headers: {'X-Requested-With': 'XMLHttpRequest'}, + headers: { 'X-Requested-With': 'XMLHttpRequest' }, xhrFields: { withCredentials: true }, @@ -82,14 +37,13 @@ $(document).ready(function () { $("#registerStatus").css('display', 'inline', 'important'); $("#registerStatus").html('
    ' + parseErrorMessage(html) + '
    '); } - - // Re-enable the register button - enableButton('#registerSubmit', 'Sign Up'); }, error: function (response) { $("#registerStatus").css('display', 'inline', 'important'); - $("#registerStatus").html('
    ' + parseErrorMessage(response.responseJSON) + '
    '); + $("#registerStatus").html('
    ' + parseErrorMessage(response.responseText) + '
    '); } + }).always(function () { + enableButton('#registerSubmit', 'Sign Up'); }); return false; }); diff --git a/Teknik/Security/CookieEventHandler.cs b/Teknik/Security/CookieEventHandler.cs new file mode 100644 index 0000000..afcc330 --- /dev/null +++ b/Teknik/Security/CookieEventHandler.cs @@ -0,0 +1,36 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Teknik.Security +{ + public class CookieEventHandler : CookieAuthenticationEvents + { + public CookieEventHandler(LogoutSessionManager logoutSessions) + { + LogoutSessions = logoutSessions; + } + + public LogoutSessionManager LogoutSessions { get; } + + public override async Task ValidatePrincipal(CookieValidatePrincipalContext context) + { + if (context.Principal.Identity.IsAuthenticated) + { + var sub = context.Principal.FindFirst("sub")?.Value; + var sid = context.Principal.FindFirst("sid")?.Value; + + if (LogoutSessions.IsLoggedOut(sub, sid)) + { + context.RejectPrincipal(); + await context.HttpContext.SignOutAsync(); + + // todo: if we have a refresh token, it should be revoked here. + } + } + } + } +} diff --git a/Teknik/Security/ITeknikPrincipal.cs b/Teknik/Security/ITeknikPrincipal.cs deleted file mode 100644 index 80017b9..0000000 --- a/Teknik/Security/ITeknikPrincipal.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Principal; -using System.Web; -using Teknik.Areas.Users.Models; - -namespace Teknik.Security -{ - public interface ITeknikPrincipal : IPrincipal - { - User Info { get; } - } -} \ No newline at end of file diff --git a/Teknik/Security/LogoutSessionManager.cs b/Teknik/Security/LogoutSessionManager.cs new file mode 100644 index 0000000..a674b4c --- /dev/null +++ b/Teknik/Security/LogoutSessionManager.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Teknik.Security +{ + public class LogoutSessionManager + { + // yes - that needs to be thread-safe, distributed etc (it's a sample) + List _sessions = new List(); + + public void Add(string sub, string sid) + { + _sessions.Add(new Session { Sub = sub, Sid = sid }); + } + + public bool IsLoggedOut(string sub, string sid) + { + var matches = _sessions.Any(s => s.IsMatch(sub, sid)); + return matches; + } + + private class Session + { + public string Sub { get; set; } + public string Sid { get; set; } + + public bool IsMatch(string sub, string sid) + { + return (Sid == sid && Sub == sub) || + (Sid == sid && Sub == null) || + (Sid == null && Sub == sub); + } + } + } +} diff --git a/Teknik/Security/PasswordHasher.cs b/Teknik/Security/PasswordHasher.cs deleted file mode 100644 index c2c4ab1..0000000 --- a/Teknik/Security/PasswordHasher.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Teknik.Areas.Users.Models; -using Teknik.Areas.Users.Utility; -using Teknik.Configuration; - -namespace Teknik.Security -{ - public class PasswordHasher : IPasswordHasher - { - private readonly Config _config; - - public PasswordHasher(Config config) - { - _config = config; - } - - public string HashPassword(User user, string password) - { - return UserHelper.GeneratePassword(_config, user, password); - } - - public PasswordVerificationResult VerifyHashedPassword(User user, string hashedPassword, string providedPassword) - { - var hashedProvidedPassword = UserHelper.GeneratePassword(_config, user, providedPassword); - if (hashedPassword == hashedProvidedPassword) - { - return PasswordVerificationResult.Success; - } - return PasswordVerificationResult.Failed; - } - } -} diff --git a/Teknik/Security/ResourceOwnerPasswordValidator.cs b/Teknik/Security/ResourceOwnerPasswordValidator.cs deleted file mode 100644 index 3f7c8c1..0000000 --- a/Teknik/Security/ResourceOwnerPasswordValidator.cs +++ /dev/null @@ -1,36 +0,0 @@ -using IdentityServer4.Validation; -using System; -using System.Collections.Generic; -using System.Linq; -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 ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator - { - private readonly TeknikEntities _dbContext; - private readonly Config _config; - - public ResourceOwnerPasswordValidator(TeknikEntities dbContext, Config config) - { - _dbContext = dbContext; - _config = config; - } - - public Task ValidateAsync(ResourceOwnerPasswordValidationContext context) - { - // Get the user - string username = context.UserName; - User user = UserHelper.GetUser(_dbContext, context.UserName); - if (user != null) - { - bool userValid = UserHelper.UserPasswordCorrect(_dbContext, _config, user, context.Password); - } - return null; - } - } -} diff --git a/Teknik/Security/RoleStore.cs b/Teknik/Security/RoleStore.cs deleted file mode 100644 index 829699a..0000000 --- a/Teknik/Security/RoleStore.cs +++ /dev/null @@ -1,91 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Microsoft.EntityFrameworkCore; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Teknik.Areas.Users.Models; -using Teknik.Configuration; -using Teknik.Data; - -namespace Teknik.Security -{ - public class RoleStore : IRoleStore - { - private readonly TeknikEntities _dbContext; - private readonly Config _config; - - public RoleStore(TeknikEntities dbContext, Config config) - { - _dbContext = dbContext; - _config = config; - } - - public async Task CreateAsync(Role role, CancellationToken cancellationToken) - { - await _dbContext.Roles.AddAsync(role); - await _dbContext.SaveChangesAsync(); - return IdentityResult.Success; - } - - public async Task DeleteAsync(Role role, CancellationToken cancellationToken) - { - _dbContext.Roles.Remove(role); - await _dbContext.SaveChangesAsync(); - return IdentityResult.Success; - } - - public async Task FindByIdAsync(string roleId, CancellationToken cancellationToken) - { - int id = int.Parse(roleId); - return _dbContext.Roles.Where(r => r.RoleId == id).FirstOrDefault(); - } - - public async Task FindByNameAsync(string normalizedRoleName, CancellationToken cancellationToken) - { - return _dbContext.Roles.Where(r => r.Name == normalizedRoleName).FirstOrDefault(); - } - - public async Task GetRoleIdAsync(Role role, CancellationToken cancellationToken) - { - return role.RoleId.ToString(); - } - - public async Task GetRoleNameAsync(Role role, CancellationToken cancellationToken) - { - return role.Name; - } - - public async Task GetNormalizedRoleNameAsync(Role role, CancellationToken cancellationToken) - { - return role.Name; - } - - public async Task SetNormalizedRoleNameAsync(Role role, string normalizedName, CancellationToken cancellationToken) - { - role.Name = normalizedName; - _dbContext.Entry(role).State = EntityState.Modified; - await _dbContext.SaveChangesAsync(); - } - - public async Task SetRoleNameAsync(Role role, string roleName, CancellationToken cancellationToken) - { - role.Name = roleName; - _dbContext.Entry(role).State = EntityState.Modified; - await _dbContext.SaveChangesAsync(); - } - - public async Task UpdateAsync(Role role, CancellationToken cancellationToken) - { - _dbContext.Entry(role).State = EntityState.Modified; - await _dbContext.SaveChangesAsync(); - return IdentityResult.Success; - } - - public void Dispose() - { - // Nothing to dispose - } - } -} diff --git a/Teknik/Security/SignInManager.cs b/Teknik/Security/SignInManager.cs deleted file mode 100644 index 6e9cb6c..0000000 --- a/Teknik/Security/SignInManager.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using System; -using System.Collections.Generic; -using System.Linq; -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 SignInManager : SignInManager - { - private readonly UserManager _userManager; - private readonly TeknikEntities _dbContext; - private readonly Config _config; - - public SignInManager( - UserManager userManager, - IHttpContextAccessor contextAccessor, - IUserClaimsPrincipalFactory claimsFactory, - IOptions optionsAccessor, - ILogger> logger, - IAuthenticationSchemeProvider schemes, - TeknikEntities dbContext, - Config config) - : base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes) - { - _userManager = userManager; - _dbContext = dbContext; - _config = config; - } - - public override async Task PasswordSignInAsync(string userName, string password, bool isPersistent, bool lockoutOnFailure) - { - User user = UserHelper.GetUser(_dbContext, userName); - if (user != null) - { - return await PasswordSignInAsync(user, password, isPersistent, lockoutOnFailure); - } - return SignInResult.Failed; - } - - public override async Task PasswordSignInAsync(User user, string password, bool isPersistent, bool lockoutOnFailure) - { - // Check to see if they are banned - if (user.AccountStatus == Utilities.AccountStatus.Banned) - { - return SignInResult.NotAllowed; - } - - return await base.PasswordSignInAsync(user, password, isPersistent, lockoutOnFailure); - } - } -} diff --git a/Teknik/Security/TeknikCookieAuthenticationEvents.cs b/Teknik/Security/TeknikCookieAuthenticationEvents.cs deleted file mode 100644 index 86d7025..0000000 --- a/Teknik/Security/TeknikCookieAuthenticationEvents.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Teknik.Areas.Users.Models; -using Teknik.Areas.Users.Utility; -using Teknik.Data; - -namespace Teknik.Security -{ - public class TeknikCookieAuthenticationEvents : CookieAuthenticationEvents - { - private readonly TeknikEntities _dbContext; - - public TeknikCookieAuthenticationEvents(TeknikEntities dbContext) - { - _dbContext = dbContext; - } - - public override async Task ValidatePrincipal(CookieValidatePrincipalContext context) - { - bool reject = false; - User user = UserHelper.GetUser(_dbContext, context.Principal.Identity.Name); - - if (user != null) - { - if (user.AccountStatus == Utilities.AccountStatus.Banned) - { - reject = true; - } - } - else - { - reject = true; - } - - if (reject) - { - context.RejectPrincipal(); - - await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); - } - } - } -} diff --git a/Teknik/Security/TeknikPrincipal.cs b/Teknik/Security/TeknikPrincipal.cs deleted file mode 100644 index 2ee4813..0000000 --- a/Teknik/Security/TeknikPrincipal.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Principal; -using System.Web; -using Teknik.Areas.Users.Models; -using Teknik.Areas.Users.Utility; -using Teknik.Data; -using Teknik.Models; -using Teknik.Utilities; - -namespace Teknik.Security -{ - public class TeknikPrincipal : ITeknikPrincipal - { - private IIdentity _Identity; - public IIdentity Identity - { - get - { - return this._Identity; - } - } - - private User m_Info = null; - public User Info - { - get - { - if (m_Info == null && Identity != null && Identity.IsAuthenticated) - { - //TeknikEntities db = new TeknikEntities(); - //m_Info = UserHelper.GetUser(db, Identity.Name); - } - return m_Info; - } - } - - public TeknikPrincipal(string username) - { - this._Identity = new GenericIdentity(username, "Forms"); - } - - public bool IsInRole(string role) - { - return UserHelper.UserHasRoles(Info, role); - } - } -} diff --git a/Teknik/Security/UserStore.cs b/Teknik/Security/UserStore.cs deleted file mode 100644 index beb2abd..0000000 --- a/Teknik/Security/UserStore.cs +++ /dev/null @@ -1,260 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Threading; -using System.Threading.Tasks; -using Teknik.Areas.Users.Models; -using Teknik.Areas.Users.Utility; -using Teknik.Configuration; -using Teknik.Data; -using Teknik.Utilities; - -namespace Teknik.Security -{ - public class UserStore : IUserStore, IUserLoginStore, IUserClaimStore, IUserPasswordStore, IUserRoleStore, IQueryableUserStore - { - private readonly TeknikEntities _dbContext; - private readonly Config _config; - - public IQueryable Users => _dbContext.Users.AsQueryable(); - - public UserStore(TeknikEntities dbContext, Config config) - { - _dbContext = dbContext; - _config = config; - } - - public void Dispose() - { - // Nothing to dispose - } - - public async Task CreateAsync(User user, CancellationToken cancellationToken) - { - UserHelper.AddAccount(_dbContext, _config, user, user.Password); - return IdentityResult.Success; - } - - public async Task DeleteAsync(User user, CancellationToken cancellationToken) - { - UserHelper.DeleteAccount(_dbContext, _config, user); - return IdentityResult.Success; - } - - public async Task FindByIdAsync(string userId, CancellationToken cancellationToken) - { - return UserHelper.GetUser(_dbContext, userId); - } - - public async Task FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken) - { - return UserHelper.GetUser(_dbContext, normalizedUserName); - } - - public async Task GetNormalizedUserNameAsync(User user, CancellationToken cancellationToken) - { - return user.Username; - } - - public async Task GetUserIdAsync(User user, CancellationToken cancellationToken) - { - return user.Username; - } - - public async Task GetUserNameAsync(User user, CancellationToken cancellationToken) - { - return user.Username; - } - - public async Task SetNormalizedUserNameAsync(User user, string normalizedName, CancellationToken cancellationToken) - { - user.Username = normalizedName; - UserHelper.EditUser(_dbContext, _config, user); - } - - public async Task SetUserNameAsync(User user, string userName, CancellationToken cancellationToken) - { - user.Username = userName; - UserHelper.EditUser(_dbContext, _config, user); - } - - public async Task UpdateAsync(User user, CancellationToken cancellationToken) - { - UserHelper.EditUser(_dbContext, _config, user); - return IdentityResult.Success; - } - - // Password Store - public async Task SetPasswordHashAsync(User user, string passwordHash, CancellationToken cancellationToken) - { - user.HashedPassword = passwordHash; - UserHelper.EditUser(_dbContext, _config, user); - } - - public async Task GetPasswordHashAsync(User user, CancellationToken cancellationToken) - { - return user.HashedPassword; - } - - public async Task HasPasswordAsync(User user, CancellationToken cancellationToken) - { - return !string.IsNullOrEmpty(user.HashedPassword); - } - - // Role Store - public async Task AddToRoleAsync(User user, string roleName, CancellationToken cancellationToken) - { - var role = _dbContext.Roles.Where(r => r.Name == roleName).FirstOrDefault(); - if (role == null) - throw new ArgumentException("Role does not exist", "roleName"); - - bool alreadyHasRole = await IsInRoleAsync(user, roleName, cancellationToken); - if (!alreadyHasRole) - { - UserRole userRole = new UserRole(); - userRole.Role = role; - userRole.User = user; - await _dbContext.UserRoles.AddAsync(userRole); - await _dbContext.SaveChangesAsync(); - } - } - - public async Task RemoveFromRoleAsync(User user, string roleName, CancellationToken cancellationToken) - { - var userRoles = user.UserRoles.Where(r => r.Role.Name == roleName).ToList(); - if (userRoles != null) - { - foreach (var userRole in userRoles) - { - _dbContext.UserRoles.Remove(userRole); - } - } - await _dbContext.SaveChangesAsync(); - } - - public async Task> GetRolesAsync(User user, CancellationToken cancellationToken) - { - return user.UserRoles.Select(ur => ur.Role.Name).ToList(); - } - - public async Task IsInRoleAsync(User user, string roleName, CancellationToken cancellationToken) - { - return UserHelper.UserHasRoles(user, roleName); - } - - public async Task> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken) - { - var userRoles = _dbContext.UserRoles.Where(r => r.Role.Name == roleName).ToList(); - if (userRoles != null) - { - return userRoles.Select(ur => ur.User).ToList(); - } - return new List(); - } - - // Login Info Store - public async Task AddLoginAsync(User user, UserLoginInfo login, CancellationToken cancellationToken) - { - LoginInfo info = new LoginInfo(); - info.LoginProvider = login.LoginProvider; - info.ProviderDisplayName = login.ProviderDisplayName; - info.ProviderKey = login.ProviderKey; - info.User = user; - - await _dbContext.UserLogins.AddAsync(info); - await _dbContext.SaveChangesAsync(); - } - - public async Task RemoveLoginAsync(User user, string loginProvider, string providerKey, CancellationToken cancellationToken) - { - var logins = user.Logins.Where(l => l.LoginProvider == loginProvider && l.ProviderKey == providerKey).ToList(); - if (logins != null) - { - foreach (var login in logins) - { - _dbContext.UserLogins.Remove(login); - } - } - await _dbContext.SaveChangesAsync(); - } - - public async Task> GetLoginsAsync(User user, CancellationToken cancellationToken) - { - List logins = new List(); - foreach (var login in user.Logins) - { - UserLoginInfo info = new UserLoginInfo(login.LoginProvider, login.ProviderKey, login.ProviderDisplayName); - logins.Add(info); - } - return logins; - } - - public async Task FindByLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken) - { - var foundLogin = _dbContext.UserLogins.Where(ul => ul.LoginProvider == loginProvider && ul.ProviderKey == providerKey).FirstOrDefault(); - if (foundLogin != null) - { - return foundLogin.User; - } - return null; - } - - // Claim Store - public async Task> GetClaimsAsync(User user, CancellationToken cancellationToken) - { - var claims = new List - { - new Claim(ClaimTypes.Name, user.Username) - }; - - // Add their roles - foreach (var role in user.UserRoles) - { - claims.Add(new Claim(ClaimTypes.Role, role.Role.Name)); - } - - return claims; - } - - public async Task AddClaimsAsync(User user, IEnumerable claims, CancellationToken cancellationToken) - { - foreach (var claim in claims) - { - if (claim.Type == ClaimTypes.Role) - { - await AddToRoleAsync(user, claim.Value, cancellationToken); - } - } - } - - public async Task ReplaceClaimAsync(User user, Claim claim, Claim newClaim, CancellationToken cancellationToken) - { - // "no" - } - - public async Task RemoveClaimsAsync(User user, IEnumerable claims, CancellationToken cancellationToken) - { - // "no" - } - - public async Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken) - { - List foundUsers = new List(); - if (claim.Type == ClaimTypes.Role) - { - var users = await GetUsersInRoleAsync(claim.Value, cancellationToken); - if (users != null && users.Any()) - foundUsers.AddRange(users); - } - else if (claim.Type == ClaimTypes.Name) - { - var user = await FindByIdAsync(claim.Value, cancellationToken); - if (user != null) - foundUsers.Add(user); - } - return foundUsers; - } - } -} diff --git a/Teknik/Startup.cs b/Teknik/Startup.cs index 1367385..6f614c1 100644 --- a/Teknik/Startup.cs +++ b/Teknik/Startup.cs @@ -23,13 +23,21 @@ using System.IO.Compression; using System.Text; using Microsoft.AspNetCore.Authentication.Cookies; using IdentityServer4.Models; -using Teknik.Areas.Accounts; using Microsoft.AspNetCore.Authentication.OpenIdConnect; -using Teknik.Security; using Teknik.Attributes; using Teknik.Filters; using Microsoft.Net.Http.Headers; using Teknik.Areas.Users.Models; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.IdentityModel.Tokens; +using Microsoft.AspNetCore.Authentication; +using IdentityModel; +using Teknik.Security; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Authorization; namespace Teknik { @@ -64,6 +72,12 @@ namespace Teknik // Create Configuration Singleton services.AddScoped(opt => Config.Load(dataDir)); + // Build an intermediate service provider + var sp = services.BuildServiceProvider(); + + // Resolve the services from the service provider + var config = sp.GetService(); + // Add Tracking Filter scopes //services.AddScoped(); //services.AddScoped(); @@ -78,77 +92,19 @@ namespace Teknik services.Configure(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. - options.CheckConsentNeeded = context => true; + options.CheckConsentNeeded = context => false; options.MinimumSameSitePolicy = Microsoft.AspNetCore.Http.SameSiteMode.None; }); - // Add Identity User - services.AddIdentity() - .AddUserStore() - .AddRoleStore() - .AddDefaultTokenProviders(); - - services.AddTransient, UserStore>(); - services.AddTransient, RoleStore>(); - services.AddTransient, PasswordHasher>(); - services.ConfigureApplicationCookie(options => { - options.Cookie.Name = "TeknikAuth"; - options.Cookie.SecurePolicy = CookieSecurePolicy.Always; + options.Cookie.Name = "TeknikWeb"; + options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict; options.Cookie.Expiration = TimeSpan.FromDays(30); options.ExpireTimeSpan = TimeSpan.FromDays(30); }); - // Identity Server - services.AddIdentityServer(options => - { - options.Events.RaiseErrorEvents = true; - options.Events.RaiseInformationEvents = true; - options.Events.RaiseFailureEvents = true; - options.Events.RaiseSuccessEvents = true; - - if (Environment.IsDevelopment()) - { - options.UserInteraction.LoginUrl = new PathString("/Login?sub=user"); - options.UserInteraction.ConsentUrl = new PathString("/Consent?sub=user"); - } - else - { - options.UserInteraction.LoginUrl = new PathString("/User/User/Login"); - options.UserInteraction.ConsentUrl = new PathString("/User/User/Consent"); - } - - // Setup Auth Cookies - options.Authentication.CheckSessionCookieName = "TeknikAuth"; - }) - .AddDeveloperSigningCredential() - .AddResourceOwnerValidator() - .AddInMemoryPersistedGrants() - .AddInMemoryIdentityResources(IdentityServerConfig.GetIdentityResources()) - .AddInMemoryApiResources(IdentityServerConfig.GetApiResources()) - .AddInMemoryClients(IdentityServerConfig.GetClients()) - .AddAspNetIdentity(); - - // Setup Authentication Service - services.AddAuthentication() - .AddIdentityServerAuthentication(options => - { - options.Authority = "http://localhost:5000"; - options.RequireHttpsMetadata = false; - - options.ApiName = "api"; - }) - .AddIdentityServerAuthentication("token", options => - { - options.Authority = "http://localhost:5000"; - options.ApiName = "api"; - - options.EnableCaching = true; - options.CacheDuration = TimeSpan.FromMinutes(10); - }); - // Compression Response services.Configure(options => options.Level = CompressionLevel.Fastest); services.AddResponseCompression(options => { @@ -168,13 +124,108 @@ namespace Teknik // Set the anti-forgery cookie name services.AddAntiforgery(options => { - options.Cookie.Name = "TeknikAntiForgery"; - options.Cookie.SecurePolicy = CookieSecurePolicy.Always; + options.Cookie.Name = "TeknikWebAntiForgery"; + options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict; }); // Core MVC services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + + services.AddTransient(); + services.AddSingleton(); + + services.AddAuthentication(options => + { + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = "oidc"; + }) + .AddIdentityServerAuthentication(options => + { + options.Authority = config.UserConfig.IdentityServerConfig.Authority; + options.RequireHttpsMetadata = true; + + options.ApiName = config.UserConfig.IdentityServerConfig.APIName; + options.ApiSecret = config.UserConfig.IdentityServerConfig.APISecret; + }) + .AddCookie(options => + { + options.ExpireTimeSpan = TimeSpan.FromMinutes(60); + options.Cookie.Name = "TeknikWebAuth"; + + options.EventsType = typeof(CookieEventHandler); + }) + .AddOpenIdConnect("oidc", options => + { + options.SignInScheme = "Cookies"; + + options.Authority = config.UserConfig.IdentityServerConfig.Authority; + options.RequireHttpsMetadata = true; + + options.ClientId = config.UserConfig.IdentityServerConfig.ClientId; + options.ClientSecret = config.UserConfig.IdentityServerConfig.ClientSecret; + options.ResponseType = "code id_token"; + + // Set the scopes to listen to + options.Scope.Clear(); + options.Scope.Add("openid"); + options.Scope.Add("role"); + options.Scope.Add("account-info"); + options.Scope.Add("security-info"); + options.Scope.Add("teknik-api.read"); + options.Scope.Add("teknik-api.write"); + options.Scope.Add("offline_access"); + + // Let's clear the claim actions and make our own mappings + options.ClaimActions.Clear(); + options.ClaimActions.MapUniqueJsonKey("sub", "sub"); + options.ClaimActions.MapUniqueJsonKey("username", "username"); + options.ClaimActions.MapUniqueJsonKey("role", "role"); + options.ClaimActions.MapUniqueJsonKey("creation-date", "creation-date"); + options.ClaimActions.MapUniqueJsonKey("last-seen", "last-seen"); + options.ClaimActions.MapUniqueJsonKey("account-type", "account-type"); + options.ClaimActions.MapUniqueJsonKey("account-status", "account-status"); + options.ClaimActions.MapUniqueJsonKey("recovery-email", "recovery-email"); + options.ClaimActions.MapUniqueJsonKey("recovery-verified", "recovery-verified"); + options.ClaimActions.MapUniqueJsonKey("2fa-enabled", "2fa-enabled"); + options.ClaimActions.MapUniqueJsonKey("pgp-public-key", "pgp-public-key"); + + options.GetClaimsFromUserInfoEndpoint = true; + options.SaveTokens = true; + + options.TokenValidationParameters = new TokenValidationParameters + { + NameClaimType = "username", + RoleClaimType = JwtClaimTypes.Role + }; + }); + + services.AddAuthorization(options => + { + options.AddPolicy("FullAPI", p => + { + p.RequireScope("teknik-api.read"); + p.RequireScope("teknik-api.write"); + }); + options.AddPolicy("ReadOnlyAPI", p => + { + p.RequireScope("teknik-api.read"); + }); + options.AddPolicy("WriteOnlyAPI", p => + { + p.RequireScope("teknik-api.read"); + }); + options.AddPolicy("AnyAPI", p => + { + p.RequireScope("teknik-api.read", "teknik-api.write"); + }); + }); + + services.Configure(x => + { + x.ValueLengthLimit = int.MaxValue; + x.MultipartBodyLengthLimit = long.MaxValue; // In case of multipart + }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -196,8 +247,8 @@ namespace Teknik Cookie = new CookieBuilder() { Domain = null, - Name = "TeknikSession", - SecurePolicy = CookieSecurePolicy.Always, + Name = "TeknikWebSession", + SecurePolicy = CookieSecurePolicy.SameAsRequest, SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict } }); @@ -240,7 +291,7 @@ namespace Teknik app.UseCookiePolicy(); // Authorize all the things! - app.UseIdentityServer(); + app.UseAuthentication(); // And finally, let's use MVC app.UseMvc(routes => diff --git a/Teknik/Teknik.csproj b/Teknik/Teknik.csproj index 8fca5f7..eeeeb23 100644 --- a/Teknik/Teknik.csproj +++ b/Teknik/Teknik.csproj @@ -27,22 +27,24 @@ - - - - - - - - - + + + + + + + + + + runtime; build; native; contentfiles; analyzers + - - + + - + @@ -54,8 +56,8 @@ - - + + @@ -67,8 +69,6 @@ - - @@ -95,15 +95,6 @@ Never PreserveNewest - - $(IncludeRazorContentInPack) - - - $(IncludeRazorContentInPack) - - - $(IncludeRazorContentInPack) -
    diff --git a/Teknik/_ViewImports.cshtml b/Teknik/_ViewImports.cshtml index 20835f9..563b5a1 100644 --- a/Teknik/_ViewImports.cshtml +++ b/Teknik/_ViewImports.cshtml @@ -2,10 +2,11 @@ @using Teknik.Configuration @using Teknik.Utilities -@using Teknik.TagHelpers +@using Teknik.Utilities.TagHelpers +@using Microsoft.AspNetCore.Http.Extensions @inject Microsoft.AspNetCore.Hosting.IHostingEnvironment HostingEnvironment @inject Config Config @addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers" -@addTagHelper "*, Teknik" \ No newline at end of file +@addTagHelper "*, Teknik.Utilities" \ No newline at end of file diff --git a/Teknik/appsettings.json b/Teknik/appsettings.json index e63e34a..62629ed 100644 --- a/Teknik/appsettings.json +++ b/Teknik/appsettings.json @@ -1,8 +1,7 @@ { "ConnectionStrings": { - "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-TeknikCore-BE9563D1-0DFB-4141-903C-287B23FF22C7;Trusted_Connection=True;MultipleActiveResultSets=true" + "TeknikEntities": "Server=(localdb)\\mssqllocaldb;Database=aspnet-TeknikCore-BE9563D1-0DFB-4141-903C-287B23FF22C7;Trusted_Connection=True;MultipleActiveResultSets=true" }, - "server.urls": "http://localhost:5000;https://localhost:5001;http://localhost:8086", "Logging": { "LogLevel": { "Default": "Warning" diff --git a/Teknik/bundleconfig.json b/Teknik/bundleconfig.json index 15b70c9..8cc39e6 100644 --- a/Teknik/bundleconfig.json +++ b/Teknik/bundleconfig.json @@ -223,16 +223,15 @@ ] }, { - "outputFileName": "./wwwroot/js/user.settings.security.min.js", + "outputFileName": "./wwwroot/js/user.settings.account.min.js", "inputFiles": [ - "./wwwroot/lib/bootstrap/js/bootstrap-switch.js", - "./wwwroot/js/app/User/SecuritySettings.js" + "./wwwroot/js/app/User/AccountSettings.js" ] }, { - "outputFileName": "./wwwroot/css/user.settings.security.min.css", + "outputFileName": "./wwwroot/js/user.settings.security.min.js", "inputFiles": [ - "./wwwroot/lib/bootstrap/css/bootstrap-switch.css" + "./wwwroot/js/app/User/SecuritySettings.js" ] }, { diff --git a/Teknik/TagHelpers/BundleTagHelper.cs b/Utilities/TagHelpers/BundleTagHelper.cs similarity index 98% rename from Teknik/TagHelpers/BundleTagHelper.cs rename to Utilities/TagHelpers/BundleTagHelper.cs index f074bf0..17bb624 100644 --- a/Teknik/TagHelpers/BundleTagHelper.cs +++ b/Utilities/TagHelpers/BundleTagHelper.cs @@ -10,7 +10,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; -namespace Teknik.TagHelpers +namespace Teknik.Utilities.TagHelpers { [HtmlTargetElement("bundle")] public class BundleTagHelper : TagHelper @@ -48,7 +48,7 @@ namespace Teknik.TagHelpers switch (_env.EnvironmentName) { - case "Staging": + case "Test": case "Production": files.Add(Src); break; diff --git a/Teknik/TagHelpers/NonceTagHelper.cs b/Utilities/TagHelpers/NonceTagHelper.cs similarity index 78% rename from Teknik/TagHelpers/NonceTagHelper.cs rename to Utilities/TagHelpers/NonceTagHelper.cs index a16464e..60cd773 100644 --- a/Teknik/TagHelpers/NonceTagHelper.cs +++ b/Utilities/TagHelpers/NonceTagHelper.cs @@ -1,13 +1,8 @@ using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Teknik.Utilities; -namespace Teknik.TagHelpers +namespace Teknik.Utilities.TagHelpers { [HtmlTargetElement("script")] public class NonceTagHelper : TagHelper diff --git a/Teknik/TagHelpers/VersionHelper.cs b/Utilities/TagHelpers/VersionHelper.cs similarity index 90% rename from Teknik/TagHelpers/VersionHelper.cs rename to Utilities/TagHelpers/VersionHelper.cs index 1d8fcf1..2095f4e 100644 --- a/Teknik/TagHelpers/VersionHelper.cs +++ b/Utilities/TagHelpers/VersionHelper.cs @@ -2,13 +2,9 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Teknik.Utilities; -namespace Teknik.TagHelpers +namespace Teknik.Utilities.TagHelpers { [HtmlTargetElement("version")] public class VersionHelper : TagHelper diff --git a/Utilities/UrlExtensions.cs b/Utilities/UrlExtensions.cs index 6ce00bc..9f7deac 100644 --- a/Utilities/UrlExtensions.cs +++ b/Utilities/UrlExtensions.cs @@ -73,7 +73,7 @@ namespace Teknik.Utilities { rightUrl = url.RouteUrl(new UrlRouteContext() { RouteName = routeName, Values = routeValues }); } - catch (ArgumentException) + catch (ArgumentException ex) { } diff --git a/Utilities/Utilities.csproj b/Utilities/Utilities.csproj index 1fa781d..84301f7 100644 --- a/Utilities/Utilities.csproj +++ b/Utilities/Utilities.csproj @@ -5,16 +5,17 @@ Teknik.Utilities Teknik.Utilities win-x86;win-x64;linux-x64;linux-arm;osx-x64 + Debug;Release;Test - - - - - + + + + + - + diff --git a/global.json b/global.json index 19a8eb8..112ce18 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "2.1.300" + "version": "2.1.403" } } \ No newline at end of file