1
0
mirror of https://git.teknik.io/Teknikode/Teknik.git synced 2023-08-02 14:16:22 +02:00

- Added ability to edit paste.

- Added session cacheing of paste password.
This commit is contained in:
Uncled1023 2019-01-24 20:40:44 -08:00
parent 063854f776
commit a4365b2671
21 changed files with 1192 additions and 37 deletions

View File

@ -1,4 +1,6 @@
namespace Teknik.Areas.API.V1.Models
using Teknik.Utilities;
namespace Teknik.Areas.API.V1.Models
{
public class PasteAPIv1Model : BaseAPIv1Model
{
@ -8,7 +10,7 @@
public string syntax { get; set; }
public string expireUnit { get; set; }
public ExpirationUnit expireUnit { get; set; }
public int expireLength { get; set; }
@ -19,7 +21,7 @@
code = null;
title = string.Empty;
syntax = "text";
expireUnit = "never";
expireUnit = ExpirationUnit.Never;
expireLength = 1;
password = string.Empty;
}

View File

@ -20,6 +20,7 @@ using Microsoft.AspNetCore.Http;
using Teknik.Logging;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Diagnostics;
namespace Teknik.Areas.Paste.Controllers
{
@ -64,6 +65,7 @@ namespace Teknik.Areas.Paste.Controllers
model.Title = paste.Title;
model.Syntax = paste.Syntax;
model.DatePosted = paste.DatePosted;
model.Username = paste.User?.Username;
if (User.Identity.IsAuthenticated && type.ToLower() == "full")
{
@ -80,6 +82,11 @@ namespace Teknik.Areas.Paste.Controllers
// The paste has a password set
if (!string.IsNullOrEmpty(paste.HashedPassword))
{
if (string.IsNullOrEmpty(password))
{
// Try to get the password from the session
password = GetCachedPassword(url);
}
string hash = string.Empty;
if (!string.IsNullOrEmpty(password))
{
@ -89,6 +96,7 @@ namespace Teknik.Areas.Paste.Controllers
if (string.IsNullOrEmpty(password) || hash != paste.HashedPassword)
{
PasswordViewModel passModel = new PasswordViewModel();
passModel.ActionUrl = Url.SubRouteUrl("p", "Paste.View");
passModel.Url = url;
passModel.Type = type;
@ -103,6 +111,9 @@ namespace Teknik.Areas.Paste.Controllers
}
}
// Save the password to the cache
CachePassword(url, password);
// Read in the file
string subDir = paste.FileName[0].ToString();
string filePath = Path.Combine(_config.PasteConfig.PasteDirectory, subDir, paste.FileName);
@ -158,7 +169,7 @@ namespace Teknik.Areas.Paste.Controllers
{
Models.Paste paste = PasteHelper.CreatePaste(_config, _dbContext, model.Content, model.Title, model.Syntax, model.ExpireUnit, model.ExpireLength ?? 1, model.Password);
if (model.ExpireUnit == "view")
if (model.ExpireUnit == ExpirationUnit.Views)
{
paste.Views = -1;
}
@ -175,6 +186,9 @@ namespace Teknik.Areas.Paste.Controllers
_dbContext.Pastes.Add(paste);
_dbContext.SaveChanges();
// Cache the password
CachePassword(paste.Url, model.Password);
return Redirect(Url.SubRouteUrl("p", "Paste.View", new { type = "Full", url = paste.Url }));
}
catch (Exception ex)
@ -186,6 +200,171 @@ namespace Teknik.Areas.Paste.Controllers
}
return View("~/Areas/Paste/Views/Paste/Index.cshtml", model);
}
public async Task<IActionResult> Edit(string url, string password)
{
Models.Paste paste = _dbContext.Pastes.Where(p => p.Url == url).FirstOrDefault();
if (paste != null)
{
if (paste.User?.Username != User.Identity.Name)
return new StatusCodeResult(StatusCodes.Status403Forbidden);
ViewBag.Title = "Edit Paste";
ViewBag.Description = "Edit your paste's content.";
// Check Expiration
if (PasteHelper.CheckExpiration(paste))
{
_dbContext.Pastes.Remove(paste);
_dbContext.SaveChanges();
return new StatusCodeResult(StatusCodes.Status404NotFound);
}
PasteViewModel model = new PasteViewModel();
model.Url = url;
model.Title = paste.Title;
model.Syntax = paste.Syntax;
model.DatePosted = paste.DatePosted;
model.Username = paste.User?.Username;
byte[] ivBytes = Encoding.Unicode.GetBytes(paste.IV);
byte[] keyBytes = AesCounterManaged.CreateKey(paste.Key, ivBytes, paste.KeySize);
// The paste has a password set
if (!string.IsNullOrEmpty(paste.HashedPassword))
{
if (string.IsNullOrEmpty(password))
{
// Try to get the password from the session
password = GetCachedPassword(url);
}
string hash = string.Empty;
if (!string.IsNullOrEmpty(password))
{
hash = PasteHelper.HashPassword(paste.Key, password);
keyBytes = AesCounterManaged.CreateKey(password, ivBytes, paste.KeySize);
}
if (string.IsNullOrEmpty(password) || hash != paste.HashedPassword)
{
PasswordViewModel passModel = new PasswordViewModel();
passModel.ActionUrl = Url.SubRouteUrl("p", "Paste.Edit");
passModel.Url = url;
if (!string.IsNullOrEmpty(password) && hash != paste.HashedPassword)
{
passModel.Error = true;
passModel.ErrorMessage = "Invalid Password";
}
// Redirect them to the password request page
return View("~/Areas/Paste/Views/Paste/PasswordNeeded.cshtml", passModel);
}
}
// Cache the password
CachePassword(url, password);
// Read in the file
string subDir = paste.FileName[0].ToString();
string filePath = Path.Combine(_config.PasteConfig.PasteDirectory, subDir, paste.FileName);
if (!System.IO.File.Exists(filePath))
{
return new StatusCodeResult(StatusCodes.Status404NotFound);
}
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
using (AesCounterStream cs = new AesCounterStream(fs, false, keyBytes, ivBytes))
using (StreamReader sr = new StreamReader(cs, Encoding.Unicode))
{
model.Content = await sr.ReadToEndAsync();
}
return View("~/Areas/Paste/Views/Paste/Edit.cshtml", model);
}
return new StatusCodeResult(StatusCodes.Status404NotFound);
}
[HttpPost]
[DisableRequestSizeLimit]
public IActionResult EditSubmit([Bind("Content, Title, Syntax, Url")]PasteEditViewModel model)
{
if (_config.PasteConfig.Enabled)
{
try
{
Models.Paste paste = _dbContext.Pastes.Where(p => p.Url == model.Url).FirstOrDefault();
if (paste != null)
{
if (paste.User?.Username != User.Identity.Name)
return new StatusCodeResult(StatusCodes.Status403Forbidden);
string password = null;
// The paste has a password set
if (!string.IsNullOrEmpty(paste.HashedPassword))
{
// Try to get the password from the session
password = GetCachedPassword(model.Url);
string hash = string.Empty;
if (!string.IsNullOrEmpty(password))
{
hash = PasteHelper.HashPassword(paste.Key, password);
}
if (string.IsNullOrEmpty(password) || hash != paste.HashedPassword)
{
PasswordViewModel passModel = new PasswordViewModel();
passModel.ActionUrl = Url.SubRouteUrl("p", "Paste.Edit");
passModel.Url = model.Url;
if (!string.IsNullOrEmpty(password) && hash != paste.HashedPassword)
{
passModel.Error = true;
passModel.ErrorMessage = "Invalid Password";
}
// Redirect them to the password request page
return View("~/Areas/Paste/Views/Paste/PasswordNeeded.cshtml", passModel);
}
}
// Delete the old file
string subDir = paste.FileName[0].ToString();
string filePath = Path.Combine(_config.PasteConfig.PasteDirectory, subDir, paste.FileName);
if (System.IO.File.Exists(filePath))
System.IO.File.Delete(filePath);
// Generate a unique file name that does not currently exist
string newFilePath = FileHelper.GenerateRandomFileName(_config.PasteConfig.PasteDirectory, _config.PasteConfig.FileExtension, 10);
string fileName = Path.GetFileName(newFilePath);
string key = PasteHelper.GenerateKey(_config.PasteConfig.KeySize);
string iv = PasteHelper.GenerateIV(_config.PasteConfig.BlockSize);
PasteHelper.EncryptContents(model.Content, newFilePath, password, key, iv, _config.PasteConfig.KeySize, _config.PasteConfig.ChunkSize);
paste.Key = key;
paste.KeySize = _config.PasteConfig.KeySize;
paste.IV = iv;
paste.BlockSize = _config.PasteConfig.BlockSize;
paste.HashedPassword = PasteHelper.HashPassword(paste.Key, password);
paste.FileName = fileName;
paste.Title = model.Title;
paste.Syntax = model.Syntax;
paste.DateEdited = DateTime.Now;
_dbContext.Entry(paste).State = EntityState.Modified;
_dbContext.SaveChanges();
return Redirect(Url.SubRouteUrl("p", "Paste.View", new { type = "Full", url = paste.Url }));
}
}
catch (Exception ex)
{
return Redirect(Url.SubRouteUrl("error", "Error.500", new { exception = ex }));
}
}
return new StatusCodeResult(StatusCodes.Status403Forbidden);
}
[HttpPost]
public IActionResult Delete(string id)
@ -206,11 +385,36 @@ namespace Teknik.Areas.Paste.Controllers
System.IO.File.Delete(filePath);
}
return Json(new { result = true });
return Json(new { result = true, redirect = Url.SubRouteUrl("p", "Paste.Index") });
}
return Json(new { error = new { message = "You do not have permission to edit this Paste" } });
}
return Json(new { error = new { message = "This Paste does not exist" } });
}
private void CachePassword(string url, string password)
{
if (HttpContext != null)
{
HttpContext.Session.Set("PastePassword_" + url, password);
}
}
private string GetCachedPassword(string url)
{
if (HttpContext != null)
{
return HttpContext.Session.Get<string>("PastePassword_" + url);
}
return null;
}
private void ClearCachedPassword(string url)
{
if (HttpContext != null)
{
HttpContext.Session.Remove("PastePassword_" + url);
}
}
}
}

View File

@ -21,6 +21,8 @@ namespace Teknik.Areas.Paste.Models
public DateTime DatePosted { get; set; }
public DateTime DateEdited { get; set; }
[CaseSensitive]
public string Url { get; set; }

View File

@ -14,7 +14,7 @@ namespace Teknik.Areas.Paste
{
public static class PasteHelper
{
public static Models.Paste CreatePaste(Config config, TeknikEntities db, string content, string title = "", string syntax = "text", string expireUnit = "never", int expireLength = 1, string password = "")
public static Models.Paste CreatePaste(Config config, TeknikEntities db, string content, string title = "", string syntax = "text", ExpirationUnit expireUnit = ExpirationUnit.Never, int expireLength = 1, string password = "")
{
Models.Paste paste = new Models.Paste();
paste.DatePosted = DateTime.Now;
@ -30,26 +30,26 @@ namespace Teknik.Areas.Paste
paste.Url = url;
// Figure out the expire date (null if 'never' or 'visit')
switch (expireUnit.ToLower())
switch (expireUnit)
{
case "never":
case ExpirationUnit.Never:
break;
case "view":
case ExpirationUnit.Views:
paste.MaxViews = expireLength;
break;
case "minute":
case ExpirationUnit.Minutes:
paste.ExpireDate = paste.DatePosted.AddMinutes(expireLength);
break;
case "hour":
case ExpirationUnit.Hours:
paste.ExpireDate = paste.DatePosted.AddHours(expireLength);
break;
case "day":
case ExpirationUnit.Days:
paste.ExpireDate = paste.DatePosted.AddDays(expireLength);
break;
case "month":
case ExpirationUnit.Months:
paste.ExpireDate = paste.DatePosted.AddMonths(expireLength);
break;
case "year":
case ExpirationUnit.Years:
paste.ExpireDate = paste.DatePosted.AddYears(expireLength);
break;
default:

View File

@ -9,6 +9,8 @@ namespace Teknik.Areas.Paste.ViewModels
{
public class PasswordViewModel : ViewModelBase
{
public string ActionUrl { get; set; }
public string Url { get; set; }
public string Type { get; set; }

View File

@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using Teknik.Utilities;
using Teknik.ViewModels;
namespace Teknik.Areas.Paste.ViewModels
@ -15,7 +16,7 @@ namespace Teknik.Areas.Paste.ViewModels
[Range(1, int.MaxValue)]
public int? ExpireLength { get; set; }
public string ExpireUnit { get; set; }
public ExpirationUnit ExpireUnit { get; set; }
public string Password { get; set; }

View File

@ -0,0 +1,16 @@
using System.ComponentModel.DataAnnotations;
using Teknik.ViewModels;
namespace Teknik.Areas.Paste.ViewModels
{
public class PasteEditViewModel : ViewModelBase
{
public string Url { get; set; }
public string Content { get; set; }
public string Title { get; set; }
public string Syntax { get; set; }
}
}

View File

@ -13,6 +13,7 @@ namespace Teknik.Areas.Paste.ViewModels
public string Password { get; set; }
public bool Hide { get; set; }
public DateTime DatePosted { get; set; }
public string Username { get; set; }
public List<Vault.Models.Vault> Vaults { get; set; }
}

View File

@ -0,0 +1,48 @@
@model Teknik.Areas.Paste.ViewModels.PasteViewModel
<bundle src="css/paste.edit.min.css" append-version="true"></bundle>
<div class="container">
<div class="row">
<div class="col-sm-10 col-sm-offset-1 text-center">
<b>@Html.ValidationSummary(true, "The input is not valid")</b>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<!form class="form-horizontal" name="editor" method="post" action="@Url.SubRouteUrl("p", "Paste.Action", new { action = "EditSubmit", url = Model.Url })">
<input type="hidden" class="form-control" name="Url" id="url" value="@Model.Url">
<div class="form-group">
<div class="col-sm-10 col-sm-offset-1">
<textarea class="form-control" name="Content" id="content" rows="20">@Model.Content</textarea>
</div>
</div>
<div class="form-group">
<label for="title" class="col-sm-2 col-sm-offset-1 control-label">Title</label>
<div class="col-sm-4">
<input type="text" class="form-control" name="Title" id="title" value="@Model.Title">
</div>
<div class="col-sm-1 col-sm-offset-2">
<button type="submit" class="btn btn-primary pull-right" id="pasteSubmit">Save</button>
</div>
</div>
<div class="form-group">
<label for="syntax" class="col-sm-2 col-sm-offset-1 control-label">Syntax</label>
<div class="col-sm-4">
<select class="form-control" name="Syntax" id="syntax">
<!option value=""@((string.IsNullOrEmpty(Model.Syntax)) ? " selected" : string.Empty)>Text</!option>
@foreach (var format in HighlightHelper.Languages.GroupBy(l => l.Value).ToList())
{
<!option value="@(format?.FirstOrDefault().Key)"@((Model.Syntax == format?.FirstOrDefault().Key) ? " selected" : string.Empty)>@(format?.Key)</!option>
}
</select>
</div>
</div>
</!form>
</div>
</div>
</div>
<bundle src="js/paste.edit.min.js" append-version="true"></bundle>

View File

@ -16,6 +16,7 @@
<script>
var createVaultURL = '@Url.SubRouteUrl("vault", "Vault.NewVaultFromService", new { type = "Paste" })';
var deletePasteURL = '@Url.SubRouteUrl("p", "Paste.Delete")';
</script>
<div class="container">
@ -27,21 +28,35 @@
<hr />
<div class="row">
<div class="col-sm-12 pull-left">
<div class="btn-group" role="group">
<a role="button" class="btn btn-default" href="@Url.SubRouteUrl("p", "Paste.Simple", new { url = Model.Url })">Simple</a>
<a role="button" class="btn btn-default" href="@Url.SubRouteUrl("p", "Paste.Raw", new { url = Model.Url })">Raw</a>
<a role="button" class="btn btn-default" href="@Url.SubRouteUrl("p", "Paste.Download", new { url = Model.Url })">Download</a>
<button type="button" class="btn btn-default" id="create-vault" data-paste-url="@Model.Url" data-paste-title="@((string.IsNullOrEmpty(Model.Title)) ? "Untitled" : Model.Title)">Create Vault</button>
@if (User.Identity.IsAuthenticated && Model.Vaults != null && Model.Vaults.Any())
<div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group">
<a role="button" class="btn btn-default" href="@Url.SubRouteUrl("p", "Paste.Simple", new { url = Model.Url })">Simple</a>
<a role="button" class="btn btn-default" href="@Url.SubRouteUrl("p", "Paste.Raw", new { url = Model.Url })">Raw</a>
<a role="button" class="btn btn-default" href="@Url.SubRouteUrl("p", "Paste.Download", new { url = Model.Url })">Download</a>
</div>
@if (User.Identity.IsAuthenticated && User.Identity.Name == Model.Username)
{
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">Add to Vault <span class="caret"></span></button>
<ul class="dropdown-menu pull-right" id="add-to-vault-menu">
@foreach (Vault item in Model.Vaults)
{
<li><a href="#" class="add-to-vault" data-add-to-vault-url="@Url.SubRouteUrl("vault", "Vault.EditVault", new { url = item.Url, type = "Paste" })" data-paste-url="@Model.Url" data-paste-title="@((string.IsNullOrEmpty(Model.Title)) ? "Untitled" : Model.Title)">@item.Title</a></li>
}
</ul>
<div class="btn-group pull-right" role="group">
<a role="button" class="btn btn-default" href="@Url.SubRouteUrl("p", "Paste.Edit", new { url = Model.Url })"><span class="text-primary">Edit</span></a>
<button type="button" class="btn btn-default" id="delete-paste" data-paste-url="@Model.Url"><span class="text-danger">Delete</span></button>
</div>
}
<div class="btn-group pull-right" role="group">
<button type="button" class="btn btn-default" id="create-vault" data-paste-url="@Model.Url" data-paste-title="@((string.IsNullOrEmpty(Model.Title)) ? "Untitled" : Model.Title)">Create Vault</button>
@if (User.Identity.IsAuthenticated)
{
@if (Model.Vaults != null && Model.Vaults.Any())
{
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">Add to Vault <span class="caret"></span></button>
<ul class="dropdown-menu pull-right" id="add-to-vault-menu">
@foreach (Vault item in Model.Vaults)
{
<li><a href="#" class="add-to-vault" data-add-to-vault-url="@Url.SubRouteUrl("vault", "Vault.EditVault", new { url = item.Url, type = "Paste" })" data-paste-url="@Model.Url" data-paste-title="@((string.IsNullOrEmpty(Model.Title)) ? "Untitled" : Model.Title)">@item.Title</a></li>
}
</ul>
}
}
</div>
</div>
</div>
</div>

View File

@ -46,13 +46,10 @@
</div>
<div class="col-sm-4" id="unit-div">
<select class="form-control" name="ExpireUnit" id="expireunit">
<!option value="never">Never</!option>
<!option value="view">Views</!option>
<!option value="minute">Minutes</!option>
<!option value="hour">Hours</!option>
<!option value="day">Days</!option>
<!option value="month">Months</!option>
<!option value="year">Years</!option>
@foreach (ExpirationUnit unit in Enum.GetValues(typeof(ExpirationUnit)))
{
<!option value="@unit">@unit.ToString()</!option>
}
</select>
</div>
</div>

View File

@ -13,7 +13,7 @@
}
<div class="row text-center">
<div class="col-sm-6 col-sm-offset-3">
<!form class="form-inline" method="post" action="@Url.SubRouteUrl("paste", "Paste.View")">
<!form class="form-inline" method="post" action="@Model.ActionUrl">
<h3>This paste is password protected</h3>
<input type="hidden" name="type" value="@Model.Type">
<input type="hidden" name="url" value="@Model.Url">

View File

@ -0,0 +1,29 @@
pre {
background-color: #ffffff;
}
pre code,
pre .line-number {
/* Ukuran line-height antara teks di dalam tag <code> dan <span class="line-number"> harus sama! */
font:normal normal 12px/14px "Courier New",Courier,Monospace;
color:black;
display:block;
}
pre .line-number {
float:left;
margin:0 1em 0 -1em;
padding-top: 5px;
border-right:1px solid;
text-align:right;
}
pre .line-number span {
display:block;
padding:0 .5em 0 1em;
}
pre .cl {
display:block;
clear:both;
}

View File

@ -0,0 +1,749 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Teknik.Data;
namespace Teknik.Data.Migrations
{
[DbContext(typeof(TeknikEntities))]
[Migration("20190124080711_PasteEditDate")]
partial class PasteEditDate
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.2.0-preview3-35497")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Teknik.Areas.Blog.Models.Blog", b =>
{
b.Property<int>("BlogId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("UserId");
b.HasKey("BlogId");
b.HasIndex("UserId");
b.ToTable("Blogs");
});
modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPost", b =>
{
b.Property<int>("BlogPostId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("Article");
b.Property<int>("BlogId");
b.Property<DateTime>("DateEdited");
b.Property<DateTime>("DatePosted");
b.Property<DateTime>("DatePublished");
b.Property<bool>("Published");
b.Property<bool>("System");
b.Property<string>("Title");
b.HasKey("BlogPostId");
b.HasIndex("BlogId");
b.ToTable("BlogPosts");
});
modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPostComment", b =>
{
b.Property<int>("BlogPostCommentId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("Article");
b.Property<int>("BlogPostId");
b.Property<DateTime>("DateEdited");
b.Property<DateTime>("DatePosted");
b.Property<int?>("UserId");
b.HasKey("BlogPostCommentId");
b.HasIndex("BlogPostId");
b.HasIndex("UserId");
b.ToTable("BlogPostComments");
});
modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPostTag", b =>
{
b.Property<int>("BlogPostTagId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("BlogPostId");
b.Property<string>("Description");
b.Property<string>("Name");
b.HasKey("BlogPostTagId");
b.HasIndex("BlogPostId");
b.ToTable("BlogPostTags");
});
modelBuilder.Entity("Teknik.Areas.Contact.Models.Contact", b =>
{
b.Property<int>("ContactId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<DateTime>("DateAdded");
b.Property<string>("Email");
b.Property<string>("Message");
b.Property<string>("Name");
b.Property<string>("Subject");
b.HasKey("ContactId");
b.ToTable("Contact");
});
modelBuilder.Entity("Teknik.Areas.Paste.Models.Paste", b =>
{
b.Property<int>("PasteId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("BlockSize");
b.Property<string>("Content");
b.Property<DateTime>("DateEdited");
b.Property<DateTime>("DatePosted");
b.Property<string>("DeleteKey")
.HasAnnotation("CaseSensitive", true);
b.Property<DateTime?>("ExpireDate");
b.Property<string>("FileName")
.HasAnnotation("CaseSensitive", true);
b.Property<string>("HashedPassword")
.HasAnnotation("CaseSensitive", true);
b.Property<string>("IV")
.HasAnnotation("CaseSensitive", true);
b.Property<string>("Key")
.HasAnnotation("CaseSensitive", true);
b.Property<int>("KeySize");
b.Property<int>("MaxViews");
b.Property<string>("Syntax");
b.Property<string>("Title");
b.Property<string>("Url")
.HasAnnotation("CaseSensitive", true);
b.Property<int?>("UserId");
b.Property<int>("Views");
b.HasKey("PasteId");
b.HasIndex("UserId");
b.ToTable("Pastes");
});
modelBuilder.Entity("Teknik.Areas.Podcast.Models.Podcast", b =>
{
b.Property<int>("PodcastId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<DateTime>("DateEdited");
b.Property<DateTime>("DatePosted");
b.Property<DateTime>("DatePublished");
b.Property<string>("Description");
b.Property<int>("Episode");
b.Property<bool>("Published");
b.Property<string>("Title");
b.HasKey("PodcastId");
b.ToTable("Podcasts");
});
modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastComment", b =>
{
b.Property<int>("PodcastCommentId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("Article");
b.Property<DateTime>("DateEdited");
b.Property<DateTime>("DatePosted");
b.Property<int>("PodcastId");
b.Property<int>("UserId");
b.HasKey("PodcastCommentId");
b.HasIndex("PodcastId");
b.HasIndex("UserId");
b.ToTable("PodcastComments");
});
modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastFile", b =>
{
b.Property<int>("PodcastFileId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<long>("ContentLength");
b.Property<string>("ContentType");
b.Property<string>("FileName");
b.Property<string>("Path");
b.Property<int>("PodcastId");
b.Property<int>("Size");
b.HasKey("PodcastFileId");
b.HasIndex("PodcastId");
b.ToTable("PodcastFiles");
});
modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastTag", b =>
{
b.Property<int>("PodcastTagId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("Description");
b.Property<string>("Name");
b.Property<int>("PodcastId");
b.HasKey("PodcastTagId");
b.HasIndex("PodcastId");
b.ToTable("PodcastTags");
});
modelBuilder.Entity("Teknik.Areas.Shortener.Models.ShortenedUrl", b =>
{
b.Property<int>("ShortenedUrlId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<DateTime>("DateAdded");
b.Property<string>("OriginalUrl");
b.Property<string>("ShortUrl")
.HasAnnotation("CaseSensitive", true);
b.Property<int?>("UserId");
b.Property<int>("Views");
b.HasKey("ShortenedUrlId");
b.HasIndex("UserId");
b.ToTable("ShortenedUrls");
});
modelBuilder.Entity("Teknik.Areas.Stats.Models.Takedown", b =>
{
b.Property<int>("TakedownId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ActionTaken");
b.Property<DateTime>("DateActionTaken");
b.Property<DateTime>("DateRequested");
b.Property<string>("Reason");
b.Property<string>("Requester");
b.Property<string>("RequesterContact");
b.HasKey("TakedownId");
b.ToTable("Takedowns");
});
modelBuilder.Entity("Teknik.Areas.Stats.Models.Transaction", b =>
{
b.Property<int>("TransactionId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<decimal>("Amount")
.HasColumnType("decimal(19, 5)");
b.Property<int>("Currency");
b.Property<DateTime>("DateSent");
b.Property<string>("Reason");
b.HasKey("TransactionId");
b.ToTable("Transactions");
});
modelBuilder.Entity("Teknik.Areas.Upload.Models.Upload", b =>
{
b.Property<int>("UploadId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("BlockSize");
b.Property<long>("ContentLength");
b.Property<string>("ContentType");
b.Property<DateTime>("DateUploaded");
b.Property<string>("DeleteKey")
.HasAnnotation("CaseSensitive", true);
b.Property<int>("Downloads");
b.Property<DateTime?>("ExpireDate");
b.Property<string>("FileName")
.HasAnnotation("CaseSensitive", true);
b.Property<string>("IV")
.HasAnnotation("CaseSensitive", true);
b.Property<string>("Key")
.HasAnnotation("CaseSensitive", true);
b.Property<int>("KeySize");
b.Property<int>("MaxDownloads");
b.Property<int?>("Takedown_TakedownId");
b.Property<string>("Url")
.HasAnnotation("CaseSensitive", true);
b.Property<int?>("UserId");
b.HasKey("UploadId");
b.HasIndex("Takedown_TakedownId");
b.HasIndex("UserId");
b.ToTable("Uploads");
});
modelBuilder.Entity("Teknik.Areas.Users.Models.InviteCode", b =>
{
b.Property<int>("InviteCodeId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<bool>("Active");
b.Property<int?>("ClaimedUserId");
b.Property<string>("Code")
.HasAnnotation("CaseSensitive", true);
b.Property<int?>("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<int>("LoginInfoId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("LoginProvider");
b.Property<string>("ProviderDisplayName");
b.Property<string>("ProviderKey");
b.Property<int>("UserId");
b.HasKey("LoginInfoId");
b.HasIndex("UserId");
b.ToTable("UserLogins");
});
modelBuilder.Entity("Teknik.Areas.Users.Models.User", b =>
{
b.Property<int>("UserId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("Username");
b.HasKey("UserId");
b.ToTable("Users");
});
modelBuilder.Entity("Teknik.Areas.Vault.Models.Vault", b =>
{
b.Property<int>("VaultId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<DateTime>("DateCreated");
b.Property<DateTime>("DateEdited");
b.Property<string>("Description");
b.Property<string>("Title");
b.Property<string>("Url");
b.Property<int?>("UserId");
b.Property<int>("Views");
b.HasKey("VaultId");
b.HasIndex("UserId");
b.ToTable("Vaults");
});
modelBuilder.Entity("Teknik.Areas.Vault.Models.VaultItem", b =>
{
b.Property<int>("VaultItemId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<DateTime>("DateAdded");
b.Property<string>("Description");
b.Property<string>("Discriminator")
.IsRequired();
b.Property<string>("Title");
b.Property<int>("VaultId");
b.HasKey("VaultItemId");
b.HasIndex("VaultId");
b.ToTable("VaultItems");
b.HasDiscriminator<string>("Discriminator").HasValue("VaultItem");
});
modelBuilder.Entity("Teknik.Areas.Vault.Models.PasteVaultItem", b =>
{
b.HasBaseType("Teknik.Areas.Vault.Models.VaultItem");
b.Property<int>("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<int>("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<int>("UserId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b1.Property<string>("Description")
.HasColumnName("Description");
b1.Property<string>("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<int>("UserId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b1.Property<bool>("Encrypt")
.HasColumnName("Encrypt");
b1.Property<int>("ExpirationLength")
.HasColumnName("ExpirationLength");
b1.Property<int>("ExpirationUnit")
.HasColumnName("ExpirationUnit");
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<int>("UserId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b1.Property<string>("About")
.HasColumnName("About");
b1.Property<string>("Quote")
.HasColumnName("Quote");
b1.Property<string>("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
}
}
}

View File

@ -0,0 +1,24 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Teknik.Data.Migrations
{
public partial class PasteEditDate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "DateEdited",
table: "Pastes",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "DateEdited",
table: "Pastes");
}
}
}

View File

@ -138,6 +138,8 @@ namespace Teknik.Data.Migrations
b.Property<string>("Content");
b.Property<DateTime>("DateEdited");
b.Property<DateTime>("DatePosted");
b.Property<string>("DeleteKey")

View File

@ -417,6 +417,13 @@ namespace Teknik
template: "Download/{url}/{password?}",
defaults: new { area = "Paste", controller = "Paste", action = "ViewPaste", type = "Download" }
);
routes.MapSubdomainRoute(
name: "Paste.Edit",
domains: new List<string>() { config.Host },
subDomains: new List<string>() { "paste", "p" },
template: "Edit/{url}/{password?}",
defaults: new { area = "Paste", controller = "Paste", action = "Edit" }
);
routes.MapSubdomainRoute(
name: "Paste.Delete",
domains: new List<string>() { config.Host },

View File

@ -0,0 +1,3 @@
$(document).ready(function () {
$('#content').focus();
});

View File

@ -1,10 +1,41 @@
/* globals createVaultURL */
/* globals createVaultURL, deletePasteURL */
$(document).ready(function () {
linkCreateVault($('#create-vault'));
$('#add-to-vault-menu').find('.add-to-vault').each(function () {
linkAddToVault($(this));
});
$('#delete-paste').click(function () {
var id = $(this).data('paste-url');
bootbox.confirm("Are you sure you want to delete this paste?", function (result) {
if (result) {
$.ajax({
type: "POST",
url: deletePasteURL,
data: { id: id },
headers: { 'X-Requested-With': 'XMLHttpRequest' },
xhrFields: {
withCredentials: true
},
success: function (response) {
if (response.result) {
window.location = response.redirect;
}
else {
$("#top_msg").css('display', 'inline', 'important');
$("#top_msg").html('<div class="alert alert-danger alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>' + parseErrorMessage(response) + '</div>');
}
},
error: function (response) {
$("#top_msg").css('display', 'inline', 'important');
$("#top_msg").html('<div class="alert alert-danger alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>' + parseErrorMessage(response.responseText) + '</div>');
}
});
}
});
});
});
function linkCreateVault(element) {

View File

@ -96,6 +96,9 @@
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="Areas\Paste\Views\Paste\Edit.cshtml">
<Pack>$(IncludeRazorContentInPack)</Pack>
</Content>
<Content Update="Areas\User\Views\User\Settings\ClientView.cshtml">
<Pack>$(IncludeRazorContentInPack)</Pack>
</Content>

View File

@ -99,6 +99,7 @@
{
"outputFileName": "./wwwroot/js/paste.view.min.js",
"inputFiles": [
"./wwwroot/lib/bootbox/js/bootbox.js",
"./wwwroot/js/app/lib/Prism/prism.js",
"./wwwroot/js/app/lib/Prism/prism-line-numbers.js",
"./wwwroot/js/app/lib/Prism/prism-line-highlight.js",
@ -113,6 +114,24 @@
"./wwwroot/css/app/lib/Prism/prism-vs.css"
]
},
{
"outputFileName": "./wwwroot/js/paste.edit.min.js",
"inputFiles": [
"./wwwroot/js/app/lib/Prism/prism.js",
"./wwwroot/js/app/lib/Prism/prism-line-numbers.js",
"./wwwroot/js/app/lib/Prism/prism-line-highlight.js",
"./wwwroot/js/app/Paste/EditPaste.js"
]
},
{
"outputFileName": "./wwwroot/css/paste.edit.min.css",
"inputFiles": [
"./wwwroot/css/app/lib/Prism/prism-line-numbers.css",
"./wwwroot/css/app/lib/Prism/prism-line-highlight.css",
"./wwwroot/css/app/lib/Prism/prism-vs.css",
"./wwwroot/css/app/Paste/EditPaste.css"
]
},
{
"outputFileName": "./wwwroot/js/podcast.min.js",
"inputFiles": [