public async Task <IActionResult> Post() { var account = await GetCurrentAccountAsync(); var auth0userId = account.Auth0UserId; // User tracking. await UserEventsService.RecordEvent(new UserEvent { Account = account, Category = "account", Name = "delete" }); // Delete the user from Auth0. // Of all the steps in this process, this one is most likely to fail, // so we do it first. await _auth0Client.DeleteUser(auth0userId); // Mark the DB data for deletion. account.IsDeleted = true; account.Auth0UserId = ""; Db.Accounts.Update(account); await Db.SaveChangesAsync(); return(Ok()); }
public UserEventsServiceTest() { tests = new AfterAndBeforeTests(); userService = new UserService(); authService = new AuthService(); constants = new TestConstants(); eventService = new EventService(); userEventsService = new UserEventsService(); }
public async Task <IActionResult> Delete(Guid id) { // Validate that the user is logged in. var account = await GetCurrentAccountAsync(); if (account == null) { return(Unauthorized()); } // Check if the ActivityGroup exists. var activityGroup = await Db.ActivityGroups.FirstOrDefaultAsync(a => a.Id == id); if (activityGroup == null) { return(NotFound()); } // Check if it belongs to the user. var pollRequest = Db.Polls.FirstOrDefaultAsync(p => p.Id == activityGroup.PollId); var poll = await pollRequest; if (poll.AccountId != account.Id) { return(NotFound()); } // User tracking. await UserEventsService.RecordEvent(new UserEvent { Account = Account, Category = "activity_group", Name = "delete" }); // Unlink the children. var children = await Db.ActivityGroups.Where(a => a.PollId == poll.Id && a.ParentId == activityGroup.Id).ToListAsync(); foreach (var child in children) { child.ParentId = null; } await Db.SaveChangesAsync(); // Delete the group. Db.ActivityGroups.Remove(activityGroup); await Db.SaveChangesAsync(); return(NoContent()); }
public async Task Delete() { var account = await GetLoggedInAccountAsync(); var auth0userId = account.Auth0UserId; // User tracking. await UserEventsService.RecordEvent(new UserEvent { Account = account, Category = "account", Name = "delete" }); // Delete the user from Auth0. // Of all the steps in this process, this one is most likely to fail, // so we do it first. await _auth0Client.DeleteUser(auth0userId); // Cancel the Paddle subscription. var paddleSubscriptionId = Account.PaddleSubscriptionId; if (paddleSubscriptionId != null) { try { await _paddleClient.CancelSubscription(paddleSubscriptionId.Value); Account.PaddleSubscriptionId = null; } catch (Exception) { _log.LogError($"Could not cancel Paddle subscription {paddleSubscriptionId} " + $"for account {Account.Id}"); } } // Mark the DB data for deletion. account.IsDeleted = true; account.Auth0UserId = ""; Db.Accounts.Update(account); await Db.SaveChangesAsync(); }
public async Task <IActionResult> GetCsv([FromQuery] DateTime fromTime, [FromQuery] DateTime toTime) { var poll = await GetDefaultPollAsync(); var entries = await _timeLogService.Get(Db, poll.Id, fromTime, toTime); var csvRecords = entries.Select(logEntry => new CsvRecord { FromTime = logEntry.FromTime, ToTime = logEntry.GetToTime(), Length = logEntry.TimeBlockLength, Activity = logEntry.EntryText }); var memoryStream = new MemoryStream(); using (var writer = new StreamWriter(memoryStream)) { using (var csv = new CsvWriter(writer)) { csv.Configuration.HasHeaderRecord = true; csv.WriteRecords(csvRecords); writer.Flush(); } } // User event tracking. await UserEventsService.RecordEvent(new UserEvent { Account = await GetCurrentAccountAsync(), Category = "daily_logs", Name = "download_csv" }); var body = memoryStream.ToArray(); return(File(body, "text/csv", "history.csv")); }
// This method gets called by the runtime. Use this method to add services // to the container. public void ConfigureServices(IServiceCollection services) { // Basic config services.AddSingleton(Configuration); services.AddOptions(); var backendRoutes = new BackendRoutes(); Configuration.GetSection("BackendRoutes").Bind(backendRoutes); services.AddSingleton(backendRoutes); var stackdriverOptions = new StackdriverOptions(); Configuration.Bind("Stackdriver", stackdriverOptions); services.AddSingleton(stackdriverOptions); // Set up the shared DataProtection keystorage when running on Google Cloud. if (!Environment.IsDevelopment()) { services.AddDataProtection() // Store keys in Cloud Storage so that multiple instances // of the web application see the same keys. .PersistKeysToGoogleCloudStorage( Configuration["DataProtection:Bucket"], Configuration["DataProtection:Object"]) // Protect the keys with Google KMS for encryption and fine- // grained access control. .ProtectKeysWithGoogleKms( Configuration["DataProtection:KmsKeyName"]); } // Set up user event tracking. IUserEventsService userEventsService; if (!Environment.IsDevelopment()) { userEventsService = new UserEventsService(stackdriverOptions); } else { userEventsService = new NullUserEventsService(); } services.AddSingleton(userEventsService); // App services var restClient = new RestClient(); services.AddSingleton <IRestClient>(restClient); var timerFactory = new SystemTimerFactory(new NullLogger()); // Use NullLogger until we get problems. services.AddSingleton <ITimerFactory>(timerFactory); var timeService = new SystemTimeService(); services.AddSingleton <ITimeService>(timeService); var tokenService = new Auth0TokenService(restClient, timerFactory, LoggerFactory.CreateLogger <Auth0TokenService>()); var auth0Client = new Auth0Client(tokenService, restClient); services.AddSingleton <IAuth0Client>(auth0Client); _accountService = new AccountService(userEventsService, LoggerFactory, timeService); services.AddSingleton(_accountService); var timeLogServie = new TimeLogService(timeService, userEventsService); services.AddSingleton <ITimeLogService>(timeLogServie); // Paddle config. var paddleClient = new PaddleClient(Configuration["Paddle:VendorId"], Configuration["Paddle:VendorAuthCode"], restClient, LoggerFactory); services.AddSingleton <IPaddleClient>(paddleClient); services.AddSingleton <IPaddleWebhookSignatureVerifier>(new PaddleWebhookSignatureVerifier()); // Configure Google App Engine logging if (!Environment.IsDevelopment()) { services.Configure <StackdriverOptions>(Configuration.GetSection("Stackdriver")); services.AddGoogleExceptionLogging(options => { options.ProjectId = stackdriverOptions.ProjectId; options.ServiceName = stackdriverOptions.ServiceName; options.Version = stackdriverOptions.Version; }); services.AddGoogleTrace(options => { options.ProjectId = stackdriverOptions.ProjectId; options.Options = TraceOptions.Create(bufferOptions: BufferOptions.NoBuffer()); }); } services.AddEntityFrameworkNpgsql() .AddDbContext <MainDbContext>() .BuildServiceProvider(); // ======= Authentication config ======= services.Configure <CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddAuthentication(options => { options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.Authority = "https://maesure.auth0.com/"; options.Audience = "https://maesure.com/api/"; }) .AddCookie(options => { options.LoginPath = "/api/auth/login"; options.LogoutPath = "/api/auth/logout"; options.SlidingExpiration = true; options.ExpireTimeSpan = TimeSpan.FromDays(90); options.Cookie.Expiration = TimeSpan.FromDays(90); options.Cookie.SameSite = SameSiteMode.Lax; // OAuth login will not work with "strict" options.Cookie.IsEssential = true; }) .AddOpenIdConnect("Auth0", options => { // Set the authority to your Auth0 domain options.Authority = $"https://{Configuration["Auth0:Domain"]}"; // Configure the Auth0 Client ID and Client Secret options.ClientId = Configuration["Auth0:ClientId"]; options.ClientSecret = Configuration["Auth0:ClientSecret"]; // Set response type to code options.ResponseType = "code"; // Configure the scope options.Scope.Clear(); options.Scope.Add("openid email profile"); // Set the callback path, so Auth0 will call back to http://localhost:5000/callback // Also ensure that you have added the URL as an Allowed Callback URL in your Auth0 dashboard // WARNING: here, "callback" is not some placeholder URL. ASP.NET expects the user to be // sent litteral "/callback" URL. Do not change this. options.CallbackPath = new PathString("/callback"); // Configure the Claims Issuer to be Auth0 options.ClaimsIssuer = "Auth0"; options.Events = new OpenIdConnectEvents { // handle the logout redirection OnRedirectToIdentityProviderForSignOut = (context) => { var logoutUri = $"https://{Configuration["Auth0:Domain"]}/v2/logout?client_id={Configuration["Auth0:ClientId"]}"; var postLogoutUri = context.Properties.RedirectUri; if (!string.IsNullOrEmpty(postLogoutUri)) { if (postLogoutUri.StartsWith("/")) { // transform to absolute var request = context.Request; postLogoutUri = request.Scheme + "://" + request.Host + postLogoutUri; } logoutUri += $"&returnTo={ Uri.EscapeDataString(postLogoutUri)}"; } context.Response.Redirect(logoutUri); context.HandleResponse(); return(Task.CompletedTask); }, OnRedirectToIdentityProvider = (context) => { // Check if we need to tell Auth0 explicitly which // connection to use. var properties = context.Properties; var connection = properties.GetString("connection"); if (connection != null) { context.ProtocolMessage.SetParameter("connection", connection); } return(Task.CompletedTask); }, OnTokenValidated = async(context) => { // Ensure that the user exists in our database. using (var db = new MainDbContext()) { // Get the Auth0 user details. var userClaims = context.SecurityToken.Claims; var auth0Id = userClaims.FirstOrDefault(c => c.Type == "sub").Value; _log.LogInformation($"Ensuring account exists for '{auth0Id}'"); // See if there's a temp account session here. var cookies = context.HttpContext.Request.Cookies; cookies.TryGetValue(PublicWebProxyController.VisitorSessionKey, out var sessionId); await _accountService.EnsureAccountEsists(db, auth0Id, sessionId); _log.LogInformation($"Finished login for '{auth0Id}'"); } }, };
public EventsController(UserEventsService userEventsService, ClientEventsService clientEventsService, IWebHostEnvironment environment) { _userEventsService = userEventsService; _clientEventsService = clientEventsService; _environment = environment; }
public async Task <IActionResult> Create([FromBody] Messages.ActivityGroupCreateRequest request) { // Validate that the user is logged in. var account = await GetCurrentAccountAsync(); if (account == null) { return(Unauthorized()); } // Basic validation. var nameError = ValidateName(request.Name); if (nameError != null) { return(nameError); } var parentError = ValidateParentDescription(request.ParentId, request.ParentMatchResponseText, request.GrandparentId, "parent"); if (parentError != null) { return(parentError); } // Data validation. var poll = await GetDefaultPollAsync(); var parentExistsError = await ValidateGroupExists(poll, request.ParentId, request.ParentMatchResponseText, request.GrandparentId, "parent"); if (parentExistsError != null) { return(parentExistsError); } // User tracking. await UserEventsService.RecordEvent(new UserEvent { Account = Account, Category = "activity_group", Name = "create" }); // Create. var newGroup = new ActivityGroup { Id = Guid.NewGuid(), ParentId = request.ParentId, Name = request.Name, PollId = poll.Id, Position = 0, }; if (!string.IsNullOrWhiteSpace(request.ParentMatchResponseText)) { var newParent = new ActivityGroup { Id = Guid.NewGuid(), ParentId = request.GrandparentId, Name = request.ParentMatchResponseText, MatchResponseText = request.ParentMatchResponseText, Position = 0, PollId = poll.Id }; newGroup.ParentId = newParent.Id; Db.ActivityGroups.Add(newParent); } Db.ActivityGroups.Add(newGroup); await Db.SaveChangesAsync(); return(NoContent()); }
public async Task <IActionResult> Move([FromBody] Messages.ActivityGroupMoveRequest request) { // Validate that the user is logged in. var account = await GetCurrentAccountAsync(); if (account == null) { return(Unauthorized()); } // Basic validation. if (request.Id != null && !string.IsNullOrWhiteSpace(request.MatchResponseText)) { return(BadRequest("Should not provide both 'id' and 'matchResponseText'")); } else if (request.Id == null && string.IsNullOrWhiteSpace(request.MatchResponseText)) { return(BadRequest("Must provide either 'id' or 'matchResponseText'")); } var nameError = ValidateLength(request.MatchResponseText, ActivityGroup.MaxMatchResponseTextLength, "matchResponseText"); if (nameError != null) { return(nameError); } var parentError = ValidateParentDescription(request.TargetParentId, request.TargetParentMatchResponseText, request.TargetGrandparentId, "targetParent"); if (parentError != null) { return(parentError); } if (request.TargetIsUncategorized != null && (request.TargetParentId != null || !string.IsNullOrWhiteSpace(request.TargetParentMatchResponseText))) { return(BadRequest("Cannot provide 'targetIsUncategorized' when providing target parent details.")); } if (!string.IsNullOrWhiteSpace(request.MatchResponseText) && request.MatchResponseText == request.TargetParentMatchResponseText) { return(BadRequest("'matchResponseText' and 'targetParentMatchResponseText' cannot be the same. " + "Cannot make an activity group its own parent.")); } // Data validation. Poll poll = null; ActivityGroup activityGroup = null; if (request.Id != null) { // This should be an actual activity group. Check if it exists. activityGroup = await Db.ActivityGroups.FirstOrDefaultAsync(g => g.Id == request.Id); if (activityGroup == null) { return(NotFound($"Could not find ActivityGroup with id = {request.Id}")); } var pollId = activityGroup.PollId; poll = await Db.Polls.FirstOrDefaultAsync(p => p.Id == pollId); if (poll == null) { throw new Exception($"Could not find a poll wit Id {pollId}"); } else if (poll.AccountId != account.Id) { return(NotFound($"Could not find ActivityGroup with id = {request.Id}")); } } else { // Confirm that this ActivityGroup does not exist. poll = await GetDefaultPollAsync(); var existingGroup = await Db.ActivityGroups.FirstOrDefaultAsync(g => g.PollId == poll.Id && g.MatchResponseText == request.MatchResponseText); if (existingGroup != null) { return(BadRequest("There is already an ActivityGroup with matchResponseText = " + $"'{request.MatchResponseText}'. Its id is {existingGroup.Id}. Please target it by its id.")); } // Create the activity group. activityGroup = new ActivityGroup { Id = Guid.NewGuid(), Name = request.MatchResponseText, MatchResponseText = request.MatchResponseText, PollId = poll.Id, Position = 0 }; Db.ActivityGroups.Add(activityGroup); } var parentExistsError = await ValidateGroupExists(poll, request.TargetParentId, request.TargetParentMatchResponseText, request.TargetGrandparentId, "target parent"); if (parentExistsError != null) { return(parentExistsError); } // Make sure that this ActivityGroup will not be its own ancestor. var nextAncestorId = request.TargetParentId; while (nextAncestorId.HasValue) { var ancestor = await Db.ActivityGroups.FirstAsync(g => g.PollId == poll.Id && g.Id == nextAncestorId.Value); if (ancestor == null) { break; } if (ancestor.Id == request.Id) { return(BadRequest("Cannot make make ActivityGroup its own ancestor.")); } nextAncestorId = ancestor.ParentId; } // User tracking. await UserEventsService.RecordEvent(new UserEvent { Account = Account, Category = "activity_group", Name = "move" }); // Perform the move. if (request.TargetParentId == null && string.IsNullOrEmpty(request.TargetParentMatchResponseText)) { // Move to the top level. activityGroup.ParentId = null; } else if (request.TargetParentId != null) { // Move it under the parent. activityGroup.ParentId = request.TargetParentId; } else if (!string.IsNullOrEmpty(request.TargetParentMatchResponseText)) { // Create the target parent. var parent = new ActivityGroup { Id = Guid.NewGuid(), Name = request.TargetParentMatchResponseText, MatchResponseText = request.TargetParentMatchResponseText, PollId = poll.Id, Position = 0 }; if (request.TargetGrandparentId != null) { parent.ParentId = request.TargetGrandparentId; } Db.ActivityGroups.Add(parent); activityGroup.ParentId = parent.Id; } else { // We should not enter here. throw new Exception("Reached unreacheable code in " + nameof(ActivityGroupsController) + ".Move()."); } await Db.SaveChangesAsync(); return(NoContent()); }
public async Task <IActionResult> CreateTempAccount() { // 1. Check whether the account exists // 2. Create account // 3. Create the poll // 4. Return // Check whether the user is already logged in. if (IsUserLoggedIn()) { throw new Exception("Cannot create a temporary account because the user already has a permanent account."); } // Check if the account exists. var found = Request.Cookies.TryGetValue(PublicWebProxyController.VisitorSessionKey, out var sessionId); if (!found) { throw new Exception($"Could not find cookie '{PublicWebProxyController.VisitorSessionKey}'"); } var existingAccount = await Db.Accounts.FirstOrDefaultAsync(a => a.TempAccountSessionId == sessionId && a.IsDeleted == false); if (existingAccount != null) { return(BadRequest($"Account for sessionId={sessionId} already exists")); } // Create the Account object in DB. // On error we just crash for now. var newAccount = new Account { Id = Guid.NewGuid(), Name = "User", Auth0UserId = "", TempAccountSessionId = sessionId }; Db.Accounts.Add(newAccount); await Db.SaveChangesAsync(); // Create a poll for the user. var poll = new Poll { Id = Guid.NewGuid(), AccountId = newAccount.Id, Name = "User poll", ActiveFrom = TimeSpan.FromHours(0), ActiveTo = TimeSpan.FromHours(24), IsActive = true, DesiredFrequency = GlobalSettings.StartingFrequencyMin, PollType = Messages.PollType.OpenText, WasStarted = true, StartedAt = DateTime.UtcNow }; Db.Polls.Add(poll); await Db.SaveChangesAsync(); // Prepare the final result. var msgPoll = new Messages.PollMsg { Id = poll.Id, IsActive = poll.IsActive, DesiredFrequency = poll.DesiredFrequency, WasStarted = poll.WasStarted, StartedAt = poll.StartedAt }; var result = new Messages.CreateTempAccountResult { User = new Messages.UserReply { AccountType = Messages.AccountType.Temporary, }, DefaultPoll = msgPoll }; // User event tracking. await UserEventsService.RecordEvent(new UserEvent { Account = newAccount, Category = "temp_account", Name = "create" }); return(Ok(result)); }