public async Task <IActionResult> Logout([FromForm] LogoutFormData request) { var existingSession = await HttpContext.Request.Cookies.GetSession(database); if (existingSession?.User == null) { return(BadRequest("You are not currently logged in")); } if (!csrfVerifier.IsValidCSRFToken(request.CSRF, existingSession.User)) { return(BadRequest("Invalid CSRF token, please try refreshing and then try again")); } // Session version doesn't need to be enforced here as logging out a session should always be safe // (after the above checks) // TODO: if an in-progress signature exists, should the session be just converted to a logged out one? database.Sessions.Remove(existingSession); await database.SaveChangesAsync(); logger.LogInformation("Session {Id} logged out", existingSession.Id); Response.Cookies.Delete(AppInfo.SessionCookieName); return(Redirect("/login")); }
public async Task InvokeAsync(HttpContext context, RequestDelegate next) { if (context.Request.Headers.TryGetValue("X-CSRF-Token", out StringValues headerValues)) { if (headerValues.Count < 1 || string.IsNullOrEmpty(headerValues[0])) { context.Response.StatusCode = StatusCodes.Status400BadRequest; await context.Response.WriteAsync("CSRF token header is empty"); return; } User?user = null; if (context.Items.TryGetValue(AppInfo.CurrentUserMiddlewareKey, out object?userRaw)) { user = userRaw as User; } if (!csrfVerifier.IsValidCSRFToken(headerValues[0], user)) { context.Response.StatusCode = StatusCodes.Status400BadRequest; await context.Response.WriteAsync("CSRF token is invalid. Please refresh and try again."); return; } context.Items[AppInfo.CSRFStatusName] = true; } else if (context.Items.ContainsKey(AppInfo.CSRFNeededName)) { // Download endpoints (for usability with direct links) don't require this if (!context.Request.Path.StartsWithSegments("/api/v1/download") && !context.Request.Path.StartsWithSegments("/api/v1/download_lfs") && !context.Request.Path.StartsWithSegments("/api/v1/feed")) { context.Response.StatusCode = StatusCodes.Status400BadRequest; await context.Response.WriteAsync("CSRF token is required for this request."); return; } } await next.Invoke(context); }
public async Task <IActionResult> Post(RegistrationFormData request) { if (!csrfVerifier.IsValidCSRFToken(request.CSRF, null, false)) { return(BadRequest("Invalid CSRF")); } if (!SecurityHelpers.SlowEquals(request.RegistrationCode, configuration.RegistrationCode)) { return(BadRequest("Invalid registration code")); } if (!request.Email.Contains('@')) { return(BadRequest("Email is invalid")); } // Check for conflicting username or email if (await database.Users.FirstOrDefaultAsync(u => u.UserName == request.Name) != null || await database.Users.FirstOrDefaultAsync(u => u.Email == request.Email) != null) { return(BadRequest("There is already an account associated with the given email or name")); } var password = Passwords.CreateSaltedPasswordHash(request.Password); var user = new User() { Email = request.Email, UserName = request.Name, PasswordHash = password, Local = true }; await database.Users.AddAsync(user); Models.User.OnNewUserCreated(user, jobClient); await database.SaveChangesAsync(); logger.LogInformation("New user registered {Name} ({Email})", request.Name, request.Email); return(Created($"/users/{user.Id}", user.GetInfo(RecordAccessLevel.Private))); }
public override async Task OnConnectedAsync() { var http = Context.GetHttpContext(); User? connectedUser = null; Session?session = null; if (http != null) { var queryParams = http.Request.Query; if (!queryParams.TryGetValue("minorVersion", out StringValues minorStr) || !queryParams.TryGetValue("majorVersion", out StringValues majorStr)) { throw new HubException("invalid connection parameters"); } if (minorStr.Count < 1 || majorStr.Count < 1) { throw new HubException("invalid connection parameters"); } string csrf; if (!queryParams.TryGetValue("access_token", out StringValues accessToken)) { // In release mode (at least I saw this happen once) the access token is in a header if (http.Request.Headers.TryGetValue("Authorization", out StringValues header) && header.Count > 0 && header[0].StartsWith("Bearer ")) { // In format "Bearer TOKEN" csrf = header[0].Split(' ').Last(); } else { throw new HubException("invalid connection parameters"); } } else { if (accessToken.Count < 1) { throw new HubException("invalid connection parameters"); } csrf = accessToken[0]; } try { session = await http.Request.Cookies.GetSession(database); connectedUser = await UserFromCookiesHelper.GetUserFromSession(session, database, true, http.Connection.RemoteIpAddress); } catch (ArgumentException) { throw new HubException("invalid session cookie"); } if (!csrfVerifier.IsValidCSRFToken(csrf, connectedUser)) { throw new HubException("invalid CSRF token"); } Context.Items["Session"] = session; bool invalidVersion = false; try { var major = Convert.ToInt32(majorStr[0]); var minor = Convert.ToInt32(minorStr[0]); if (major != AppInfo.Major || minor != AppInfo.Minor) { invalidVersion = true; } } catch (Exception) { throw new HubException("invalid connection parameters"); } if (invalidVersion) { await Clients.Caller.ReceiveVersionMismatch(); } } if (connectedUser != null && session == null) { throw new Exception("Logic error! user is not null but session is null"); } Context.Items["User"] = connectedUser; await base.OnConnectedAsync(); if (connectedUser == null) { await Clients.Caller.ReceiveOwnUserInfo(null); } else { if (session == null) { throw new Exception("logic error, session is null when connected user is not null"); } // All sessions listen to notifications about them await Groups.AddToGroupAsync(Context.ConnectionId, NotificationGroups.SessionImportantMessage + session.Id); await Clients.Caller.ReceiveOwnUserInfo(connectedUser.GetInfo( connectedUser.HasAccessLevel(UserAccessLevel.Admin) ? RecordAccessLevel.Admin : RecordAccessLevel.Private)); // Could send some user specific notices here // await Clients.Caller.ReceiveSiteNotice(SiteNoticeType.Primary, "hey you connected"); } }