The next generation of the Teknik Services. Written in ASP.NET. https://www.teknik.io/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1542 lines
64 KiB

using System;
using System.Collections.Generic;
using System.Linq;
using Teknik.Areas.Users.Models;
using Teknik.Areas.Users.ViewModels;
using Teknik.Controllers;
using Teknik.Utilities;
using Teknik.Areas.Users.Utility;
using Teknik.Filters;
using QRCoder;
using TwoStepsAuthenticator;
using Teknik.Attributes;
using Teknik.Utilities.Cryptography;
using Microsoft.Extensions.Logging;
using Teknik.Configuration;
using Teknik.Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Net;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using System.Threading.Tasks;
using Teknik.Logging;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication;
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;
using IdentityServer4.Models;
using Teknik.Utilities.Routing;
using Teknik.BillingCore;
namespace Teknik.Areas.Users.Controllers
{
[Authorize]
[Area("User")]
[ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)]
public class UserController : DefaultController
{
private static readonly UsedCodesManager usedCodesManager = new UsedCodesManager();
private const string _AuthSessionKey = "AuthenticatedUser";
private readonly IHttpContextAccessor _httpContextAccessor;
private ISession _session => _httpContextAccessor.HttpContext.Session;
public UserController(ILogger<Logger> logger, Config config, TeknikEntities dbContext, IHttpContextAccessor httpContextAccessor) : base(logger, config, dbContext)
{
_httpContextAccessor = httpContextAccessor;
}
[HttpGet]
[AllowAnonymous]
public IActionResult Index()
{
return Redirect(Url.SubRouteUrl("www", "Home.Index"));
}
[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]
[TrackPageView]
public async Task Logout()
{
// these are the sub & sid to signout
//var sub = User.FindFirst("sub")?.Value;
//var sid = User.FindFirst("sid")?.Value;
await HttpContext.SignOutAsync("Cookies");
await HttpContext.SignOutAsync("oidc");
//_logoutSessions.Add(sub, sid);
}
[HttpGet]
[AllowAnonymous]
[TrackPageView]
public IActionResult Register(string inviteCode, string ReturnUrl)
{
RegisterViewModel model = new RegisterViewModel();
model.InviteCode = inviteCode;
model.ReturnUrl = ReturnUrl;
return View("/Areas/User/Views/User/ViewRegistration.cshtml", model);
}
[HttpOptions]
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Register([Bind(Prefix = "Register")]RegisterViewModel model)
{
model.Error = false;
model.ErrorMessage = string.Empty;
if (ModelState.IsValid)
{
if (_config.UserConfig.RegistrationEnabled)
{
if (!model.Error && !UserHelper.ValidUsername(_config, model.Username))
{
model.Error = true;
model.ErrorMessage = "That username is not valid";
}
if (!model.Error && !(await UserHelper.UsernameAvailable(_dbContext, _config, model.Username)))
{
model.Error = true;
model.ErrorMessage = "That username is not available";
}
if (!model.Error && string.IsNullOrEmpty(model.Password))
{
model.Error = true;
model.ErrorMessage = "You must enter a password";
}
if (!model.Error && model.Password.Length < _config.UserConfig.MinPasswordLength)
{
model.Error = true;
model.ErrorMessage = $"Password must be at least {_config.UserConfig.MinPasswordLength} characters long";
}
if (!model.Error && model.Password != model.ConfirmPassword)
{
model.Error = true;
model.ErrorMessage = "Passwords must match";
}
// Validate the Invite Code
if (!model.Error && _config.UserConfig.InviteCodeRequired && string.IsNullOrEmpty(model.InviteCode))
{
model.Error = true;
model.ErrorMessage = "An Invite Code is required to register";
}
if (!model.Error && !string.IsNullOrEmpty(model.InviteCode) && _dbContext.InviteCodes.Where(c => c.Code == model.InviteCode && c.Active && c.ClaimedUser == null).FirstOrDefault() == null)
{
model.Error = true;
model.ErrorMessage = "Invalid Invite Code";
}
if (!model.Error)
{
try
{
await UserHelper.CreateAccount(_dbContext, _config, Url, model.Username, model.Password, model.RecoveryEmail, model.InviteCode);
}
catch (Exception ex)
{
model.Error = true;
model.ErrorMessage = ex.GetFullMessage(true);
}
if (!model.Error)
{
// Let's log them in
return GenerateActionResult(new { success = true, redirectUrl = Url.SubRouteUrl("account", "User.Login", new { returnUrl = model.ReturnUrl }) }, Redirect(Url.SubRouteUrl("account", "User.Login", new { returnUrl = model.ReturnUrl })));
}
}
}
if (!model.Error)
{
model.Error = true;
model.ErrorMessage = "User Registration is Disabled";
}
}
else
{
model.Error = true;
model.ErrorMessage = "Missing Required Fields";
}
return GenerateActionResult(new { error = model.ErrorMessage }, View("/Areas/User/Views/User/ViewRegistration.cshtml", model));
}
// GET: Profile/Profile
[AllowAnonymous]
[TrackPageView]
public async Task<IActionResult> ViewProfile(string username)
{
if (string.IsNullOrEmpty(username))
{
username = User.Identity.Name;
}
ProfileViewModel model = new ProfileViewModel();
ViewBag.Title = "User Does Not Exist";
ViewBag.Description = "The User does not exist";
try
{
User user = UserHelper.GetUser(_dbContext, username);
if (user != null)
{
ViewBag.Title = username + "'s Profile";
ViewBag.Description = "Viewing " + username + "'s Profile";
model.UserID = user.UserId;
model.Username = user.Username;
if (_config.EmailConfig.Enabled)
{
model.Email = string.Format("{0}@{1}", user.Username, _config.EmailConfig.Domain);
}
// 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.BlogSettings = user.BlogSettings;
model.UploadSettings = user.UploadSettings;
model.Uploads = _dbContext.Uploads.Where(u => u.UserId == user.UserId).OrderByDescending(u => u.DateUploaded).ToList();
model.Pastes = _dbContext.Pastes.Where(u => u.UserId == user.UserId).OrderByDescending(u => u.DatePosted).ToList();
model.ShortenedUrls = _dbContext.ShortenedUrls.Where(s => s.UserId == user.UserId).OrderByDescending(s => s.DateAdded).ToList();
model.Vaults = _dbContext.Vaults.Where(v => v.UserId == user.UserId).OrderByDescending(v => v.DateCreated).ToList();
return View(model);
}
model.Error = true;
model.ErrorMessage = "The user does not exist";
}
catch (Exception ex)
{
model.Error = true;
model.ErrorMessage = ex.GetFullMessage(true);
}
return View(model);
}
[TrackPageView]
public IActionResult ViewServiceData()
{
string username = User.Identity.Name;
ViewServiceDataViewModel model = new ViewServiceDataViewModel();
ViewBag.Title = "User Does Not Exist";
ViewBag.Description = "The User does not exist";
try
{
User user = UserHelper.GetUser(_dbContext, username);
if (user != null)
{
ViewBag.Title = "Service Data";
ViewBag.Description = "Viewing all of your service data";
model.CurrentUploadStorage = _dbContext.Uploads.Where(u => u.UserId == user.UserId).Sum(u => u.ContentLength);
model.MaxUploadStorage = user.UploadSettings.MaxUploadStorage ?? _config.UploadConfig.MaxStorage;
model.Uploads = _dbContext.Uploads.Where(u => u.UserId == user.UserId).OrderByDescending(u => u.DateUploaded).ToList();
model.Pastes = _dbContext.Pastes.Where(u => u.UserId == user.UserId).OrderByDescending(u => u.DatePosted).ToList();
model.ShortenedUrls = _dbContext.ShortenedUrls.Where(s => s.UserId == user.UserId).OrderByDescending(s => s.DateAdded).ToList();
model.Vaults = _dbContext.Vaults.Where(v => v.UserId == user.UserId).OrderByDescending(v => v.DateCreated).ToList();
return View(model);
}
model.Error = true;
model.ErrorMessage = "The user does not exist";
}
catch (Exception ex)
{
model.Error = true;
model.ErrorMessage = ex.GetFullMessage(true);
}
return View(model);
}
[TrackPageView]
public IActionResult Settings()
{
return Redirect(Url.SubRouteUrl("account", "User.ProfileSettings"));
}
[TrackPageView]
public IActionResult ProfileSettings()
{
string username = User.Identity.Name;
User user = UserHelper.GetUser(_dbContext, username);
if (user != null)
{
ViewBag.Title = "Profile Settings";
ViewBag.Description = "Your " + _config.Title + " Profile Settings";
ProfileSettingsViewModel model = new ProfileSettingsViewModel();
model.Page = "Profile";
model.UserID = user.UserId;
model.Username = user.Username;
model.About = user.UserSettings.About;
model.Quote = user.UserSettings.Quote;
model.Website = user.UserSettings.Website;
return View("/Areas/User/Views/User/Settings/ProfileSettings.cshtml", model);
}
return new StatusCodeResult(StatusCodes.Status403Forbidden);
}
[TrackPageView]
public IActionResult AccountSettings()
{
string username = User.Identity.Name;
User user = UserHelper.GetUser(_dbContext, username);
if (user != null)
{
ViewBag.Title = "Account Settings";
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 new StatusCodeResult(StatusCodes.Status403Forbidden);
}
[TrackPageView]
public IActionResult BillingSettings()
{
string username = User.Identity.Name;
User user = UserHelper.GetUser(_dbContext, username);
if (user != null)
{
ViewBag.Title = "Billing Settings";
ViewBag.Description = "Your " + _config.Title + " Billing Settings";
BillingSettingsViewModel model = new BillingSettingsViewModel();
model.Page = "Billing";
model.UserID = user.UserId;
model.Username = user.Username;
if (user.BillingCustomer != null)
{
var billingService = BillingFactory.GetBillingService(_config.BillingConfig);
var portalSession = billingService.CreatePortalSession(user.BillingCustomer.CustomerId, Url.SubRouteUrl("account", "User.BillingSettings"));
model.PortalUrl = portalSession.Url;
var subs = billingService.GetSubscriptionList(user.BillingCustomer.CustomerId);
foreach (var sub in subs)
{
foreach (var price in sub.Prices)
{
var product = billingService.GetProduct(price.ProductId);
var subView = new SubscriptionViewModel()
{
SubscriptionId = sub.Id,
ProductId = product.ProductId,
ProductName = product.Name,
Storage = price.Storage,
Price = price.Amount,
Interval = price.Interval.ToString(),
BillingPeriodEnd = sub.BillingPeriodEnd,
Canceled = sub.CancelAtBillingEnd
};
model.Subscriptions.Add(subView);
}
}
}
return View("/Areas/User/Views/User/Settings/BillingSettings.cshtml", model);
}
return new StatusCodeResult(StatusCodes.Status403Forbidden);
}
[TrackPageView]
public async Task<IActionResult> SecuritySettings()
{
string username = User.Identity.Name;
User user = UserHelper.GetUser(_dbContext, username);
if (user != null)
{
ViewBag.Title = "Security Settings";
ViewBag.Description = "Your " + _config.Title + " Security Settings";
SecuritySettingsViewModel model = new SecuritySettingsViewModel();
model.Page = "Security";
model.UserID = user.UserId;
model.Username = user.Username;
// Get the user secure info
IdentityUserInfo userInfo = await IdentityHelper.GetIdentityUserInfo(_config, user.Username);
//model.TrustedDeviceCount = user.TrustedDevices.Count;
//model.AuthTokens = new List<AuthTokenViewModel>();
//foreach (AuthToken token in user.AuthTokens)
//{
// AuthTokenViewModel tokenModel = new AuthTokenViewModel();
// tokenModel.AuthTokenId = token.AuthTokenId;
// tokenModel.Name = token.Name;
// tokenModel.LastDateUsed = token.LastDateUsed;
// model.AuthTokens.Add(tokenModel);
//}
model.PgpPublicKey = userInfo.PGPPublicKey;
model.RecoveryEmail = userInfo.RecoveryEmail;
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 new StatusCodeResult(StatusCodes.Status403Forbidden);
}
[TrackPageView]
public async Task<IActionResult> DeveloperSettings()
{
string username = User.Identity.Name;
User user = UserHelper.GetUser(_dbContext, username);
if (user != null)
{
ViewBag.Title = "Developer Settings";
ViewBag.Description = "Your " + _config.Title + " Developer Settings";
DeveloperSettingsViewModel model = new DeveloperSettingsViewModel();
model.Page = "Developer";
model.UserID = user.UserId;
model.Username = user.Username;
model.AuthTokens = new List<AuthTokenViewModel>();
model.Clients = new List<ClientViewModel>();
//foreach (AuthToken token in user.AuthTokens)
//{
// AuthTokenViewModel tokenModel = new AuthTokenViewModel();
// tokenModel.AuthTokenId = token.AuthTokenId;
// tokenModel.Name = token.Name;
// tokenModel.LastDateUsed = token.LastDateUsed;
// model.AuthTokens.Add(tokenModel);
//}
Client[] clients = await IdentityHelper.GetClients(_config, username);
foreach (Client client in clients)
{
model.Clients.Add(new ClientViewModel()
{
Id = client.ClientId,
Name = client.ClientName,
HomepageUrl = client.ClientUri,
LogoUrl = client.LogoUri,
CallbackUrl = string.Join(',', client.RedirectUris),
AllowedScopes = client.AllowedScopes,
GrantType = IdentityHelper.GrantsToGrantType(client.AllowedGrantTypes.ToArray())
});
}
return View("/Areas/User/Views/User/Settings/DeveloperSettings.cshtml", model);
}
return new StatusCodeResult(StatusCodes.Status403Forbidden);
}
[TrackPageView]
public IActionResult InviteSettings()
{
string username = User.Identity.Name;
User user = UserHelper.GetUser(_dbContext, username);
if (user != null)
{
ViewBag.Title = "Invite Settings";
ViewBag.Description = "Your " + _config.Title + " Invite Settings";
InviteSettingsViewModel model = new InviteSettingsViewModel();
model.Page = "Invite";
model.UserID = user.UserId;
model.Username = user.Username;
List<InviteCodeViewModel> availableCodes = new List<InviteCodeViewModel>();
List<InviteCodeViewModel> claimedCodes = new List<InviteCodeViewModel>();
if (user.OwnedInviteCodes != null)
{
foreach (InviteCode inviteCode in user.OwnedInviteCodes.Where(c => c.Active))
{
InviteCodeViewModel inviteCodeViewModel = new InviteCodeViewModel();
inviteCodeViewModel.ClaimedUser = inviteCode.ClaimedUser;
inviteCodeViewModel.Active = inviteCode.Active;
inviteCodeViewModel.Code = inviteCode.Code;
inviteCodeViewModel.InviteCodeId = inviteCode.InviteCodeId;
inviteCodeViewModel.Owner = inviteCode.Owner;
if (inviteCode.ClaimedUser == null)
availableCodes.Add(inviteCodeViewModel);
if (inviteCode.ClaimedUser != null)
claimedCodes.Add(inviteCodeViewModel);
}
}
model.AvailableCodes = availableCodes;
model.ClaimedCodes = claimedCodes;
return View("/Areas/User/Views/User/Settings/InviteSettings.cshtml", model);
}
return new StatusCodeResult(StatusCodes.Status403Forbidden);
}
[TrackPageView]
public IActionResult BlogSettings()
{
string username = User.Identity.Name;
User user = UserHelper.GetUser(_dbContext, username);
if (user != null)
{
ViewBag.Title = "Blog Settings";
ViewBag.Description = "Your " + _config.Title + " Blog Settings";
BlogSettingsViewModel model = new BlogSettingsViewModel();
model.Page = "Blog";
model.UserID = user.UserId;
model.Username = user.Username;
model.Title = user.BlogSettings.Title;
model.Description = user.BlogSettings.Description;
return View("/Areas/User/Views/User/Settings/BlogSettings.cshtml", model);
}
return new StatusCodeResult(StatusCodes.Status403Forbidden);
}
[TrackPageView]
public IActionResult UploadSettings()
{
string username = User.Identity.Name;
User user = UserHelper.GetUser(_dbContext, username);
if (user != null)
{
ViewBag.Title = "Upload Settings";
ViewBag.Description = "Your " + _config.Title + " Upload Settings";
UploadSettingsViewModel model = new UploadSettingsViewModel();
model.Page = "Upload";
model.UserID = user.UserId;
model.Username = user.Username;
model.MaxStorage = user.UploadSettings.MaxUploadStorage ?? _config.UploadConfig.MaxStorage;
model.MaxFileSize = user.UploadSettings.MaxUploadFileSize ?? _config.UploadConfig.MaxUploadFileSize;
model.Encrypt = user.UploadSettings.Encrypt;
model.ExpirationLength = user.UploadSettings.ExpirationLength;
model.ExpirationUnit = user.UploadSettings.ExpirationUnit;
return View("/Areas/User/Views/User/Settings/UploadSettings.cshtml", model);
}
return new StatusCodeResult(StatusCodes.Status403Forbidden);
}
[HttpGet]
[AllowAnonymous]
[TrackPageView]
public async Task<IActionResult> ViewRawPGP(string username)
{
ViewBag.Title = username + "'s Public Key";
ViewBag.Description = "The PGP public key for " + username;
IdentityUserInfo userClaims = await IdentityHelper.GetIdentityUserInfo(_config, username);
if (!string.IsNullOrEmpty(userClaims.PGPPublicKey))
{
return Content(userClaims.PGPPublicKey, "text/plain");
}
return new StatusCodeResult(StatusCodes.Status404NotFound);
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult EditBlog(BlogSettingsViewModel settings)
{
if (ModelState.IsValid)
{
try
{
User user = UserHelper.GetUser(_dbContext, User.Identity.Name);
if (user != null)
{
// Blogs
user.BlogSettings.Title = settings.Title;
user.BlogSettings.Description = settings.Description;
UserHelper.EditAccount(_dbContext, _config, user);
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 IActionResult EditProfile(ProfileSettingsViewModel settings)
{
if (ModelState.IsValid)
{
try
{
User user = UserHelper.GetUser(_dbContext, User.Identity.Name);
if (user != null)
{
// Profile Info
user.UserSettings.Website = settings.Website;
user.UserSettings.Quote = settings.Quote;
user.UserSettings.About = settings.About;
UserHelper.EditAccount(_dbContext, _config, user);
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<IActionResult> EditSecurity(SecuritySettingsViewModel settings)
{
if (ModelState.IsValid)
{
try
{
User user = UserHelper.GetUser(_dbContext, User.Identity.Name);
if (user != null)
{
// PGP Key valid?
if (!string.IsNullOrEmpty(settings.PgpPublicKey) && !PGP.IsPublicKey(settings.PgpPublicKey))
{
return Json(new { error = "Invalid PGP Public Key" });
}
// Get the user secure info
IdentityUserInfo userInfo = await IdentityHelper.GetIdentityUserInfo(_config, user.Username);
if (userInfo.PGPPublicKey != settings.PgpPublicKey)
{
var result = await IdentityHelper.UpdatePGPPublicKey(_config, user.Username, settings.PgpPublicKey);
if (!result.Success)
return Json(new { error = result.Message });
}
if (userInfo.RecoveryEmail != settings.RecoveryEmail)
{
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))
{
string resetUrl = Url.SubRouteUrl("account", "User.ResetPassword", new { Username = user.Username });
string verifyUrl = Url.SubRouteUrl("account", "User.VerifyRecoveryEmail", new { Username = user.Username, Code = WebUtility.UrlEncode(token) });
UserHelper.SendRecoveryEmailVerification(_config, user.Username, settings.RecoveryEmail, resetUrl, verifyUrl);
}
}
//if (!settings.TwoFactorEnabled && (!userInfo.TwoFactorEnabled.HasValue || userInfo.TwoFactorEnabled.Value))
//{
// var result = await IdentityHelper.Disable2FA(_config, user.Username);
// if (!result.Success)
// return Json(new { error = result.Message });
//}
//UserHelper.EditAccount(_dbContext, _config, user, changePass, settings.NewPassword);
//if (!oldTwoFactor && settings.TwoFactorEnabled)
//{
// return Json(new { result = new { checkAuth = true, key = newKey, qrUrl = Url.SubRouteUrl("account", "User.Action", new { action = "GenerateAuthQrCode", key = newKey }) } });