public static async Task RefreshUserTokensAsync(PerformContext context) { context.WriteLine("Looking for tokens to refresh!"); using (var scope = Startup.ServiceProvider.CreateScope()) { MSSQLDB db = scope.ServiceProvider.GetRequiredService <MSSQLDB>(); IConfiguration configuration = scope.ServiceProvider.GetRequiredService <IConfiguration>(); var soonExpiringUsers = await db.ExecuteListAsync <Shared.Models.User.Profile>( @"SELECT * FROM user_profile WHERE DATEDIFF(MINUTE, GETUTCDATE(), CAST(JSON_VALUE(user_settings, '$.TokenExpiration') as DATETIMEOFFSET)) < 10 AND last_notification_mail IS NULL AND deleted = 0" ); context.WriteLine($"Found {soonExpiringUsers.Count} user(s) to refresh tokens for"); IHttpClientFactory _hcf = scope.ServiceProvider.GetRequiredService <IHttpClientFactory>(); var hc = _hcf.CreateClient(); foreach (var user in soonExpiringUsers.WithProgress(context)) { var res = await hc.PostAsync("https://auth.frontierstore.net/token", new FormUrlEncodedContent(new Dictionary <string, string> { { "grant_type", "refresh_token" }, { "refresh_token", user.UserSettings.RefreshToken.ToString() }, { "client_id", configuration["EliteDangerous:ClientId"] } }) ); if (!res.IsSuccessStatusCode) { // The user is not authorized to perform more automatic refreshes of the token // Send notification to the user that they need to re-login if they want to keep getting their journals stored if (!string.IsNullOrWhiteSpace(user.NotificationEmail)) { await SendLoginNotificationMethod.SendLoginNotification(db, configuration, user); } } else { // We managed to grab a new token, lets save it! var tokenInfo = JsonSerializer.Deserialize <OAuth2Response>(await res.Content.ReadAsStringAsync()); var settings = new Settings { AuthToken = tokenInfo.AccessToken, TokenExpiration = DateTimeOffset.UtcNow.AddSeconds(tokenInfo.ExpiresIn), RefreshToken = tokenInfo.RefreshToken, FrontierProfile = user.UserSettings.FrontierProfile }; // Update user with new token info await db.ExecuteNonQueryAsync("UPDATE user_profile SET user_settings = @settings, last_notification_mail = NULL WHERE user_identifier = @userIdentifier", new SqlParameter("@settings", JsonSerializer.Serialize(settings)), new SqlParameter("@userIdentifier", user.UserIdentifier) ); } } } }
public static async Task DownloadJournalAsync(Guid userIdentifier, PerformContext context) { using (var rlock = new RedisJobLock($"JournalDownloader.DownloadJournal.{userIdentifier}")) { if (!rlock.TryTakeLock()) { return; } context.WriteLine($"Looking for journals for user {userIdentifier}"); using (var scope = Startup.ServiceProvider.CreateScope()) { MSSQLDB db = scope.ServiceProvider.GetRequiredService <MSSQLDB>(); IConfiguration configuration = scope.ServiceProvider.GetRequiredService <IConfiguration>(); MinioClient minioClient = scope.ServiceProvider.GetRequiredService <MinioClient>(); var discordClient = scope.ServiceProvider.GetRequiredService <DiscordWebhook>(); var user = await db.ExecuteSingleRowAsync <Profile>( @"SELECT * FROM user_profile WHERE user_identifier = @user_identifier AND last_notification_mail IS NULL AND deleted = 0 AND skip_download = 0", new SqlParameter("user_identifier", userIdentifier) ); if (user == null) { return; } var authToken = user.UserSettings.AuthToken; var hc = SharedSettings.GetHttpClient(scope); hc.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authToken); hc.BaseAddress = new Uri("https://companion.orerve.net"); var profile = await GetProfileAsync(hc); var profileJson = await profile.Content.ReadAsStringAsync(); if (!profile.IsSuccessStatusCode || string.IsNullOrWhiteSpace(profileJson)) { context.WriteLine($"Invalid statuscode: {profile.StatusCode} {profile.ReasonPhrase}"); bool resetAuth = false; switch (profile.StatusCode) { case HttpStatusCode.BadRequest: // User does not potentially own the game resetAuth = true; break; case HttpStatusCode.Unauthorized: // Invalid token (or Epic games) resetAuth = true; break; } if (string.IsNullOrWhiteSpace(profileJson)) { resetAuth = true; } if (resetAuth) { await SSEActivitySender.SendUserActivityAsync(user.UserIdentifier, "Could not authorize you", "Sorry, but there seems to be something wrong with your account. Please contact us so we can try and figure out what's wrong!" ); context.WriteLine("Bailing out early, user doesn't own Elite or has issues with cAPI auth"); if (!string.IsNullOrWhiteSpace(user.NotificationEmail)) { context.WriteLine("User cannot be fetched, asking them to reauthenticate"); await SendLoginNotificationMethod.SendLoginNotification(db, configuration, user); } } return; } var profileData = JsonSerializer.Deserialize <EliteProfile>(profileJson); context.WriteLine(profileJson); if (profileJson == "{}") { if (!string.IsNullOrWhiteSpace(user.NotificationEmail)) { context.WriteLine("User cannot be fetched, asking them to reauthenticate"); await SendLoginNotificationMethod.SendLoginNotification(db, configuration, user); } return; } context.WriteLine($"Downloading journals for {profileData.Commander.Name}"); DateTime journalDate = DateTime.Today.AddDays(-25); await SSEActivitySender.SendUserActivityAsync(user.UserIdentifier, "Downloading your journals", "We're beginning to download your journals now, a few notifications may pop up." ); while (journalDate.Date != DateTime.Today) { context.WriteLine($"Fetching data for {journalDate.ToString("yyyy-MM-dd")}"); var req = await TryGetJournalAsync(discordClient, journalDate, user, db, hc, minioClient, context); if (req.shouldBail) { // Failed to get loop journal context.WriteLine($"Bailing because of errors"); return; } journalDate = journalDate.AddDays(1); } context.WriteLine($"Fetching data for {journalDate.ToString("yyyy-MM-dd")}"); var reqOut = await TryGetJournalAsync(discordClient, journalDate, user, db, hc, minioClient, context); if (reqOut.shouldBail) { // Failed to get loop journal context.WriteLine($"Bailing because of errors"); return; } if (user.SendToEDDN && !RedisJobLock.IsLocked($"EDDNUserUploader.UploadAsync.{user.UserIdentifier}")) { var userJournals = await db.ExecuteScalarAsync <long>( "SELECT COUNT_BIG(journal_id) FROM user_journal WHERE user_identifier = @user_identifier AND sent_to_eddn = 0 AND last_processed_line_number >= sent_to_eddn_line", new SqlParameter("user_identifier", userIdentifier) ); if (userJournals > 0) { context.WriteLine($"Sending {userJournals} journals to EDDN"); BackgroundJob.Enqueue(() => EDDNUserUploader.UploadAsync(user.UserIdentifier, profileData.Commander.Name, null)); } } if (user.IntegrationSettings.ContainsKey("EDSM") && user.IntegrationSettings["EDSM"].GetTypedObject <EDSMIntegrationSettings>().Enabled) { var userJournals = await db.ExecuteScalarAsync <long>( "SELECT COUNT_BIG(journal_id) FROM user_journal WHERE user_identifier = @user_identifier AND ISNULL(JSON_VALUE(integration_data, '$.EDSM.lastSentLineNumber'), '0') < last_processed_line_number AND last_processed_line_number > 0 AND ISNULL(JSON_VALUE(integration_data, '$.EDSM.fullySent'), 'false') = 'false'", new SqlParameter("user_identifier", userIdentifier) ); if (userJournals > 0) { if (!RedisJobLock.IsLocked($"EDSMUserUploader.UploadAsync.{user.UserIdentifier}")) { context.WriteLine($"Sending {userJournals} journals to EDSM"); BackgroundJob.Enqueue(() => EDSMUserUploader.UploadAsync(user.UserIdentifier, null)); } } } if (user.IntegrationSettings.ContainsKey("Canonn R&D") && user.IntegrationSettings["Canonn R&D"].GetTypedObject <CanonnRDIntegrationSettings>().Enabled) { var userJournals = await db.ExecuteScalarAsync <long>( "SELECT COUNT_BIG(journal_id) FROM user_journal WHERE user_identifier = @user_identifier AND ISNULL(JSON_VALUE(integration_data, '$.\"Canonn R\\u0026D\".lastSentLineNumber'), '0') <= last_processed_line_number AND last_processed_line_number > 0 AND ISNULL(JSON_VALUE(integration_data, '$.\"Canonn R\\u0026D\".fullySent'), 'false') = 'false'", new SqlParameter("user_identifier", userIdentifier) ); if (userJournals > 0) { if (!RedisJobLock.IsLocked($"CanonnRDUserUploader.UploadAsync.{user.UserIdentifier}")) { context.WriteLine($"Sending {userJournals} journals to Canonn"); BackgroundJob.Enqueue(() => CanonnRDUserUploader.UploadAsync(user.UserIdentifier, profileData.Commander.Name, null)); } } } context.WriteLine("All done!"); } } }