using System.Text;
using System.Text.Encodings.Web;
using System.Web;
using LiteDB;
using Newtonsoft.Json;
using Tesses.WebServer;
namespace SimpleNotes
{
public class Note
{
public Note()
{
}
public Note(string title,string body)
{
Title = title;
Body = body;
}
[JsonProperty("id")]
public long Id {get;set;}
[JsonProperty("title")]
public string Title {get;set;}="";
[JsonProperty("body")]
public string Body {get;set;}="";
}
public static class Program
{
const string Index = "
{0} {2}{0} {1}";
const string CreateAccount = "";
const string ChangePassword = "";
const string Css = "";
static string CreateIndex(string title,string page,string extraHead="")
{
return string.Format(Index,HttpUtility.HtmlEncode(title),page,$"{extraHead}");
}
static string A(string href,string content,bool escapeContent=true)
{
return $"{(escapeContent ? HttpUtility.HtmlEncode(content) : content)} ";
}
static string NewNoteForm()
{
return "";
}
static string ExistingNoteForm(Note note)
{
return $"{HttpUtility.HtmlEncode(note.Body)} ";
}
static string MainIndex(IEnumerable notes)
{
StringBuilder page = new StringBuilder();
page.Append($"{A("/adduser","Add User")} {A("/changepassword","Change Password")} ");
page.Append(NewNoteForm());
page.Append("");
foreach(var item in notes)
{
page.Append($"{A($"./?id={item.Id}",item.Title)} {A($"./delete?id={item.Id}","Delete")} ");
}
page.Append(" ");
return CreateIndex("SimpleNotes",page.ToString());
}
public static string GetUserName(this ServerContext ctx)
{
string username = "";
if (ctx.RequestHeaders.TryGetFirst("Authorization", out var auth))
{
string[] authorization = auth.Split(' ');
//authorization_basic
if (authorization[0] == "Basic")
{
string[] userPass = Encoding.UTF8.GetString(Convert.FromBase64String(authorization[1])).Split(new char[] { ':' },2);
//return userPass.Equals($"{config.UserName}:{config.Password}", StringComparison.Ordinal);
username = userPass[0];
}
}
return username;
}
private static string Clean(string text)
{
StringBuilder builder=new StringBuilder();
foreach(var c in text)
{
if(c > sbyte.MaxValue)
{
byte[] data = Encoding.UTF8.GetBytes($"{c}");
foreach(var d in data)
{
builder.Append($"${d.ToString("X2")}");
}
}else
if(!char.IsLetterOrDigit(c) && c != '$' && c != '_')
{
byte data = (byte)c;
builder.Append($"${data.ToString("X2")}");
}
else if(c == '$')
{
builder.Append("$$");
}
else
{
builder.Append(c);
}
}
return builder.ToString();
}
public static ILiteCollection GetCollection2(this ILiteDatabase db,ServerContext ctx)
{
return db.GetCollection($"notes_{Clean(ctx.GetUserName())}",BsonAutoId.ObjectId);
}
public static void Main(string[] args)
{
Directory.CreateDirectory("data");
using ILiteDatabase db = new LiteDatabase("data/mydb.db");
RouteServer routeServer = new RouteServer();
routeServer.Add("/adduser",async(ctx)=>{
await ctx.SendTextAsync(CreateIndex("Add User",CreateAccount));
});
routeServer.Add("/changepassword",async(ctx)=>{
await ctx.SendTextAsync(CreateIndex("Change Password",ChangePassword));
});
routeServer.Add("/changepassword",async(ctx)=>{
ctx.ParseBody();
if(ctx.QueryParams.TryGetFirst("old_password",out var old_password) && ctx.QueryParams.TryGetFirst("password",out var password) && ctx.QueryParams.TryGetFirst("password_confirm",out var password_confirm) && password==password_confirm)
{
if(User.ChangePassword(db.GetCollection("users"),ctx.GetUserName(),old_password,password))
{
await ctx.SendRedirectAsync("/");
}
else
{
await ctx.SendTextAsync(CreateIndex("Failed",A("/adduser","<- Back")));
}
}
else
{
await ctx.SendTextAsync(CreateIndex("Failed",A("/adduser","<- Back")));
}
},"POST");
routeServer.Add("/adduser",async(ctx)=>{
ctx.ParseBody();
if(ctx.QueryParams.TryGetFirst("username",out var username) && ctx.QueryParams.TryGetFirst("password",out var password) && ctx.QueryParams.TryGetFirst("password_confirm",out var password_confirm) && password==password_confirm)
{
if(User.AddUser(db.GetCollection("users"),username,password))
{
await ctx.SendRedirectAsync("/");
}
else
{
await ctx.SendTextAsync(CreateIndex("Failed",A("/adduser","<- Back")));
}
}
else
{
await ctx.SendTextAsync(CreateIndex("Failed",A("/adduser","<- Back")));
}
},"POST");
foreach(var meth in new[]{"GET","POST"})
routeServer.Add("/",RootPage,meth);
async Task RootPage(ServerContext ctx)
{
if(ctx.Method == "POST")
{
ctx.ParseBody();
}
if(ctx.QueryParams.TryGetFirst("id",out var id) && long.TryParse(id,out var idV))
{
var item=db.GetCollection2(ctx).FindById(idV);
if(item == null)
{
await ctx.SendRedirectAsync("/");
}
else
{
if(ctx.Method == "POST")
{
if(ctx.QueryParams.TryGetFirst("title",out var title) && ctx.QueryParams.TryGetFirst("body",out var body))
{
item.Title = title;
item.Body = body;
db.GetCollection2(ctx).Update(item);
}
}
await ctx.SendTextAsync(CreateIndex(item.Title,ExistingNoteForm(item)));
}
}
else
{
if(ctx.Method == "POST")
{
if(ctx.QueryParams.TryGetFirst("title",out var title) && ctx.QueryParams.TryGetFirst("body",out var body))
{
var item = new Note();
item.Title = title;
item.Body = body;
db.GetCollection2(ctx).Insert(item);
}
}
await ctx.SendTextAsync(MainIndex(db.GetCollection2(ctx).FindAll()));
}
}
async Task ApiNote(ServerContext ctx)
{
try{
if(ctx.Method != "DELETE")
{
if(ctx.Method == "POST")
{
Note n = await ctx.ReadJsonAsync();
var f = n.Id == 0 ? null : db.GetCollection2(ctx).FindById(n.Id);
if(f == null)
n.Id = (long)db.GetCollection2(ctx).Insert(n);
else
db.GetCollection2(ctx).Update(n);
await ctx.SendJsonAsync(new{success=true,note=n});
}
else if (ctx.Method == "GET")
{
bool justKeysBool=false;
if(ctx.QueryParams.TryGetFirst("justkeys",out var justKeys) && justKeys == "true")
justKeysBool=true;
if(ctx.QueryParams.TryGetFirst("id",out var id) && long.TryParse(id,out var idV))
{
Note note = db.GetCollection2(ctx).FindById(idV);
if(note == null)
await ctx.SendJsonAsync(new{success=false});
else
await ctx.SendJsonAsync(new{success=true,note = note});
}
else
{
if(justKeysBool)
{
}
else
{
List notes = new List();
foreach(var item in db.GetCollection2(ctx).FindAll())
{
notes.Add(item);
}
await ctx.SendJsonAsync(new{success=true,notes=notes});
}
}
}
}
else
{
if(ctx.QueryParams.TryGetFirst("id",out var id) && long.TryParse(id,out var idV))
{
bool res=db.GetCollection2(ctx).Delete(idV);
await ctx.SendJsonAsync(new {
success=res,
});
}
else
{
int v=db.GetCollection2(ctx).DeleteAll();
await ctx.SendJsonAsync(new {
success=true,
deleted=v
});
}
}
}catch(Exception ex)
{
Console.WriteLine(ex);
await ctx.SendJsonAsync(new{success=false,exception=ex});
}
}
foreach(var meth in new[]{"GET","POST","DELETE"})
routeServer.Add("/api/note",ApiNote,meth);
routeServer.Add("/delete",async(ctx)=>{
// /delete?confirm=true
if(ctx.QueryParams.TryGetFirst("id",out var id) && long.TryParse(id,out var idV))
{
if(ctx.QueryParams.TryGetFirst("confirm",out var confirm) && confirm == "true")
{
db.GetCollection2(ctx).Delete(idV);
await ctx.SendRedirectAsync("/");
}
else
{
await ctx.SendTextAsync(CreateIndex("Delete note?",$"{A($"/delete?id={idV}&confirm=true","Yes")} {A("/","No")}"));
}
}
else
{
if(ctx.QueryParams.TryGetFirst("confirm",out var confirm) && confirm == "true")
{
db.GetCollection2(ctx).DeleteAll();
await ctx.SendRedirectAsync("/");
}
else
{
await ctx.SendTextAsync(CreateIndex("Delete note?",$"{A($"/delete?confirm=true","Yes")} {A("/","No")}"));
}
}
},"GET");
BasicAuthServer authServer=new BasicAuthServer((user,pass)=>{
var userColl = db.GetCollection("users");
return User.ValidUser(userColl,user,pass);
},routeServer,"NotesRealm");
authServer.StartServer(19428);
}
}
public class User
{
public long Id {get;set;}
public string Username {get;set;}="";
public string Salt {get;set;}="";
public string HashedPassword {get;set;}="";
public static bool ValidUser(ILiteCollection users,string username,string password)
{
if(users.Count() == 0)
{
return AddUser(users,username,password);
}
else
{
var user = FindUser(users,username);
if(user == null) return false;
return user.CorrectPassword(password);
}
}
public bool CorrectPassword(string password)
{
string passwordWithSalt = $"{Salt}{password}";
return HashedPassword==Convert.ToBase64String(System.Security.Cryptography.SHA512.HashData(System.Text.Encoding.UTF8.GetBytes(passwordWithSalt)));
}
public static bool ValidPassword(string password)
{
return password.Length >= 12;
}
public void SetPassword(string password)
{
byte[] salt=System.Security.Cryptography.RandomNumberGenerator.GetBytes(32);
Salt = Convert.ToBase64String(salt);
string passwordWithSalt = $"{Salt}{password}";
HashedPassword=Convert.ToBase64String(System.Security.Cryptography.SHA512.HashData(System.Text.Encoding.UTF8.GetBytes(passwordWithSalt)));
}
public static User FindUser(ILiteCollection users,string username)
{
return users.FindOne(e=>e.Username==username);
}
public static bool AddUser(ILiteCollection users,string username,string password)
{
if(username.Contains(':')) return false;
if(FindUser(users,username) != null)
return false;
User user = new User();
user.Username = username;
if(!ValidPassword(password)) return false;
user.SetPassword(password);
users.Insert(user);
return true;
}
public static bool ChangePassword(ILiteCollection users,string username,string oldPassword,string newPassword)
{
var user=FindUser(users,username);
if(user == null) return false;
if(!user.CorrectPassword(oldPassword)) return false;
if(!ValidPassword(newPassword)) return false;
user.SetPassword(newPassword);
users.Update(user);
return true;
}
}
}