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 $"


"; } static string MainIndex(IEnumerable notes) { StringBuilder page = new StringBuilder(); page.Append($"{A("/adduser","Add User")} {A("/changepassword","Change Password")}
"); page.Append(NewNoteForm()); 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; } } }