public static void Main(string[] args) { List <RegisterDelegate> endPoints = new List <RegisterDelegate>(); CaveDb db = new CaveDb("mongodb://192.168.5.10"); Func <HttpContext, Action <UserSession>, Task> handleSession; Func <bool> isCommandLine = () => false; UserVerb.Start(db, isCommandLine, endPoints, out handleSession); CaveVerb.Start(db, isCommandLine, endPoints, handleSession); AspNetVerb.Start(endPoints.ToArray()); }
public static void Start(CaveDb db, Func <bool> isCommandLine, List <RegisterDelegate> endPoints, Func <HttpContext, Action <UserSession>, Task> handleSession) { endPoints.Get("/cave", async(ctx) => { await handleSession(ctx, async session => { var dbCaves = db.Caves.Find("{}").ToList(); var caves = dbCaves .Select(c => new { c.Id, Number = c.CaveNumber, c.Name, c.Description, c.SubType, CaveData = c.Data ?? new List <Data>(), Locations = c.Locations ?? new List <CaveLocation>(), Notes = c.Notes ?? new List <CaveNote>() }) .ToArray(); var response = new { Caves = caves, Status = 200 }; ctx.WriteHeader(Mimes.Json, System.Net.HttpStatusCode.OK); await ctx.WriteBodyObject(response); }); }); endPoints.Put("/cave", async(ctx) => { // upsert a cave await handleSession(ctx, async session => { var request = await ctx.ReadBody <CaveUpdateRequest>(); Cave cave = null; if (!string.IsNullOrEmpty(request.CaveId)) { cave = db.Caves.Find(c => c.Id == request.CaveId).SingleOrDefault(); } if (null == cave) { int caveNumber = db.GetNextCaveNumber(); cave = new Cave() { Id = Guid.NewGuid().ToString(), Name = $"CC #{caveNumber}", Description = string.Empty, CreatedDate = DateTime.Now, CaveNumber = caveNumber }; db.Caves.InsertOne(cave); db.Users.UpdateOne(u => u.Id == session.UserId, Builders <User> .Update.AddToSet(u => u.Caves, cave.Id)); db.History.InsertOne(UserVerb.HistoryEntry(session.UserId, cave.Id, null, null, $"Created new cave {cave.Name}:{cave.Id}")); } cave.Name = request.Name; cave.SubType = request.SubType; cave.Description = request.Description ?? string.Empty; cave.IsDeleted = false; if (request.Data != null) { cave.Data = request.Data.ToList(); } if (request.Locations != null) { cave.Locations = request.Locations.ToList(); if (cave.Locations.Count > 0 && cave.Locations.All(c => c.IsActive == false)) { cave.Locations[0].IsActive = true; } } if (request.Notes != null) { cave.Notes = request.Notes.ToList(); } db.History.InsertOne(UserVerb.HistoryEntry(session.UserId, cave.Id, null, null, $"Cave {cave.Id} updated by {session.User.Name}")); db.Caves.ReplaceOne(Builders <Cave> .Filter.Eq(c => c.Id, cave.Id), cave); // get list of all associated media var deadMedia = new HashSet <string>(); var deadMediaInt = new HashSet <int>(); foreach (var caveMedia in db.Media.AsQueryable().Where(m => m.AttachId == cave.Id && m.AttachType == "cave")) { bool notFound = true; var reference = $"src=\"/Media/{caveMedia.Id}\""; // check to see if it's contained in one of the notes foreach (var n in request.Notes) { if (n.Note.Contains(reference)) { notFound = false; break; } } if (notFound) { if (caveMedia.OldId.HasValue) { deadMediaInt.Add(caveMedia.OldId.Value); } else { deadMedia.Add(caveMedia.Id); } } } if (deadMedia.Count > 0) { // remove the database entries db.Media.DeleteMany(m => deadMedia.Contains(m.Id)); } var response = new { Status = HttpStatusCode.OK }; ctx.WriteHeader(Mimes.Json, HttpStatusCode.OK); await ctx.WriteBodyObject(response); }); }); }
public static LoginResponse Login(CaveDb db, Func <bool> isCommandLine, Func <int, byte[]> getRandomBytes, LoginRequest request) { // find user var user = db.Users.Find(u => u.Email == request.Email).FirstOrDefault(); if (null == user) { HistoryEntry(null, null, null, null, "Failed login for bad user {0}", request.Email); return(new LoginResponse() { Status = (int)HttpStatusCode.Unauthorized, StatusDescription = "Username or Password is incorrect" }); } // only allow the admin password via the command line if (user.Email == "admin" && !isCommandLine()) { HistoryEntry(null, null, null, null, "Failed login for bad user {0}", request.Email); return(new LoginResponse() { Status = (int)HttpStatusCode.Unauthorized, StatusDescription = "Username or Password is incorrect" }); } // verify password var hash = HashPassword(request.Password, user.PasswordSalt); if (hash != user.PasswordHash) { HistoryEntry(null, null, null, null, "Failed login for user {0}. Bad password.", user); return(new LoginResponse() { Status = (int)HttpStatusCode.Unauthorized, StatusDescription = "Username or Password is incorrect" }); } var sessionKey = getRandomBytes(8); var now = DateTime.UtcNow; // create session UserSession session = new UserSession() { Id = Guid.NewGuid().ToString(), IsCommandLine = isCommandLine(), SessionId = Convert.ToBase64String(sessionKey), Timeout = now.AddHours(1), UserId = user.Id, User = user }; var deadSessions = db.UserSessions.DeleteMany(s => s.Timeout < now); db.UserSessions.InsertOne(session); db.Users.UpdateOne(u => u.Id == session.User.Id, Builders <User> .Update.Set(u => u.LastLoggedIn, now)); db.History.InsertOne(HistoryEntry(user.Id, null, null, null, "User {0} logged in.", user)); return(new LoginResponse() { SessionId = session.SessionId, UserId = user.Id, Name = user.Name, Profile = user.Profile, Permissions = user.Permissions, Status = (int)HttpStatusCode.OK }); }
public static Func <HttpContext, Action <UserSession>, Task> BuildHandleSession(CaveDb db, Func <bool> isCommandLine) { return(async(ctx, handler) => { string sessionId = ctx.Request.Headers["Authorization"]; UserSession session = null; bool isExpired = false; do { var sessionQuery = db.UserSessions.AsQueryable(); if (isCommandLine()) { session = sessionQuery.FirstOrDefault(s => s.IsCommandLine); } else { session = sessionQuery.FirstOrDefault(s => s.SessionId == sessionId); } if (null != session && session.Timeout < DateTime.UtcNow) { db.UserSessions.DeleteOne(s => s.Id == session.Id); isExpired = true; } else { isExpired = false; } }while (isExpired); if (session != null) { session.Timeout = DateTime.UtcNow.AddHours(1); db.UserSessions.UpdateOne(s => s.Id == session.Id, Builders <UserSession> .Update.Set(s => s.Timeout, session.Timeout)); session.User = db.Users.Find(u => u.Id == session.UserId).First(); handler(session); } else { ctx.Response.StatusCode = (int)HttpStatusCode.Unauthorized; } }); }
public static void Start(CaveDb db, Func <bool> isCommandLine, List <RegisterDelegate> endPoints, out Func <HttpContext, Action <UserSession>, Task> handleSession) { RandomNumberGenerator rng = RandomNumberGenerator.Create(); Func <int, byte[]> getRng = cnt => { byte[] data = new byte[cnt]; rng.GetBytes(data); return(data); }; Func <HttpContext, Action <UserSession>, Task> innerHandleSession = BuildHandleSession(db, isCommandLine); handleSession = innerHandleSession; Task handleResponse <T>(HttpContext ctx, Func <UserSession, T, Task> action) where T : Response, new() { return(innerHandleSession(ctx, async session => { T response = new T(); response.SetSuccess(); try { await action(session, response); } catch (Exception ex) { response.SetFail(HttpStatusCode.InternalServerError, $"Exception thrown: {ex.Message} \r\n {ex.StackTrace}"); } ctx.WriteHeader(Mimes.Json, HttpStatusCode.OK); await ctx.WriteBodyObject(response); })); } // define endpoints endPoints.Post("/login", async ctx => { // read body var login = await ctx.ReadBody <LoginRequest>(); // get login var loginResponse = Login(db, isCommandLine, getRng, login); // return body ctx.WriteHeader(Mimes.Json, HttpStatusCode.OK); await ctx.WriteBodyObject(loginResponse); }); endPoints.Get("/user/profile", async ctx => { await innerHandleSession(ctx, async session => { ctx.WriteHeader(Mimes.Json, HttpStatusCode.OK); await ctx.WriteBodyObject(new { session.UserId, session.User.Email, session.User.Profile, session.User.Permissions, Status = 200 }); }); }); endPoints.Get("/user", async ctx => { await handleResponse <ItemsResponse <User> >(ctx, async(session, response) => { if (!session.User.Permissions.Contains('A')) { ctx.Unauthorized(); return; } var users = db.Users.Find(u => true).ToList().ToArray(); response.Items = users; }); }); endPoints.Put("/user", async ctx => { await handleResponse <Response>(ctx, async(session, response) => { if (!session.User.Permissions.Contains('A')) { ctx.Unauthorized(); return; } var request = await ctx.ReadBody <UserUpdateRequest>(); var user = db.Users.Find(u => u.Id == request.Id).FirstOrDefault(); if (user == null) { user = new User(); user.Id = Guid.NewGuid().ToString("n"); db.Users.InsertOne(user); } user.Name = request.Name; user.Email = request.Email; user.Profile = request.Profile; user.Permissions = request.Permissions; db.Users.ReplaceOne(u => u.Id == user.Id, user); }); }); endPoints.Put("/user/new", async ctx => { await handleResponse <ItemResponse <NewUser> >(ctx, async(session, response) => { UserNewRequest request = await ctx.ReadBody <UserNewRequest>(); if (string.IsNullOrWhiteSpace(request.Email)) { response.SetFail("No email address specified"); return; } if (db.Users.Find(u => u.Email == request.Email).Any()) { response.SetFail("Email already exists."); return; } if (string.IsNullOrWhiteSpace(request.Password)) { request.Password = GenerateUserPassword(); } User user = new User() { Name = request.Name, Email = request.Email, IsActive = true, Created = DateTime.UtcNow, Profile = request.Profile, Permissions = request.Permissions, PasswordSalt = Convert.ToBase64String(getRng(16)), }; user.PasswordHash = HashPassword(request.Password, user.PasswordSalt); db.Users.InsertOne(user); response.Item = new NewUser() { Email = user.Email, Id = user.Id, Name = user.Name, Password = request.Password, Permissions = user.Permissions, Profile = user.Profile }; }); }); endPoints.Delete("/user/{id}", async ctx => { await handleResponse <Response>(ctx, async(session, response) => { string id = ctx.Request.RouteValues["id"].ToString(); if (db.Users.DeleteOne(u => u.Id == id).DeletedCount == 0) { response.SetFail("$No user with id {id}"); return; } db.History.InsertOne(HistoryEntry(id, null, null, null, "User Deleted")); }); }); endPoints.Get("/user/password/reset/{id}", async ctx => { await handleResponse <UserResetPasswordResponse>(ctx, async(session, response) => { string id = ctx.Request.RouteValues["id"].ToString(); User user = db.Users.Find(u => u.Id == id).FirstOrDefault(); if (user == null) { response.SetFail($"User {id} not found."); return; } response.NewPassword = GenerateUserPassword(); byte[] salt = getRng(16); user.PasswordSalt = Convert.ToBase64String(salt); user.PasswordHash = HashPassword(response.NewPassword, user.PasswordSalt); db.Users.ReplaceOne(u => u.Id == user.Id, user); }); }); }