security: untrack DataProtection keys and runtime exports; remove dead legacy controllers
- git rm --cached on committed DataProtection key XMLs (keys/, JobTrackerApi/keys/) and daily export JSON snapshots; extend .gitignore so runtime data (keys, exports, CV artifacts/exports/benchmarks) can never be committed again. - Delete root Controller/ stubs: an early prototype compiled by no project (JobTrackerApi excludes them; JobTrackerBackend globs only JobTrackerApi/Controllers). - NOTE: the removed key XMLs remain in git history; rotating DataProtection keys on the server is recommended. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -46,6 +46,13 @@ todo jobtracker.txt
|
||||
tmp/
|
||||
/tmp/
|
||||
|
||||
# Runtime data that must never be committed (DataProtection keys, exports, CV artifacts)
|
||||
keys/
|
||||
JobTrackerApi/exports/
|
||||
JobTrackerApi/CvArtifacts/
|
||||
JobTrackerApi/CvExports/
|
||||
JobTrackerApi/CvBenchmarks/
|
||||
|
||||
# Local app data
|
||||
*.db
|
||||
*.db-*
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class AttachmentsController : ControllerBase
|
||||
{
|
||||
private readonly IWebHostEnvironment _env;
|
||||
public AttachmentsController(IWebHostEnvironment env) => _env = env;
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Upload([FromForm] IFormFileCollection files, [FromForm] int jobId)
|
||||
{
|
||||
var folder = Path.Combine(_env.ContentRootPath, "Attachments", jobId.ToString());
|
||||
Directory.CreateDirectory(folder);
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
var path = Path.Combine(folder, file.FileName);
|
||||
using var stream = new FileStream(path, FileMode.Create);
|
||||
await file.CopyToAsync(stream);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using JobTrackerApi.Data;
|
||||
using JobTrackerApi.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace JobTrackerApi.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class CompaniesController : ControllerBase
|
||||
{
|
||||
private readonly JobTrackerContext _context;
|
||||
public CompaniesController(JobTrackerContext context) => _context = context;
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IEnumerable<Company>> Get() =>
|
||||
await _context.Companies.Include(c => c.Jobs).ToListAsync();
|
||||
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<Company>> Post(Company company)
|
||||
{
|
||||
_context.Companies.Add(company);
|
||||
await _context.SaveChangesAsync();
|
||||
return CreatedAtAction(nameof(Get), new { id = company.Id }, company);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using JobTrackerApi.Data;
|
||||
using JobTrackerApi.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace JobTrackerApi.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class CorrespondenceController : ControllerBase
|
||||
{
|
||||
private readonly JobTrackerContext _context;
|
||||
public CorrespondenceController(JobTrackerContext context) => _context = context;
|
||||
|
||||
// GET all messages for a job
|
||||
[HttpGet("{jobId}")]
|
||||
public async Task<IEnumerable<Correspondence>> GetForJob(int jobId)
|
||||
{
|
||||
return await _context.Correspondences
|
||||
.Where(c => c.JobApplicationId == jobId)
|
||||
.OrderBy(c => c.Date)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
// POST new message
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<Correspondence>> Post(Correspondence message)
|
||||
{
|
||||
_context.Correspondences.Add(message);
|
||||
await _context.SaveChangesAsync();
|
||||
return CreatedAtAction(nameof(GetForJob), new { jobId = message.JobApplicationId }, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using JobTrackerApi.Data;
|
||||
using JobTrackerApi.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace JobTrackerApi.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class JobApplicationsController : ControllerBase
|
||||
{
|
||||
private readonly JobTrackerContext _context;
|
||||
public JobApplicationsController(JobTrackerContext context) => _context = context;
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IEnumerable<JobApplication>> Get() =>
|
||||
await _context.JobApplications.Include(j => j.Company).ToListAsync();
|
||||
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<JobApplication>> Post(JobApplication job)
|
||||
{
|
||||
_context.JobApplications.Add(job);
|
||||
await _context.SaveChangesAsync();
|
||||
return CreatedAtAction(nameof(Get), new { id = job.Id }, job);
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> Put(int id, JobApplication updatedJob)
|
||||
{
|
||||
var job = await _context.JobApplications.FindAsync(id);
|
||||
if (job == null) return NotFound();
|
||||
|
||||
job.JobTitle = updatedJob.JobTitle;
|
||||
job.Status = updatedJob.Status;
|
||||
job.ResponseReceived = updatedJob.ResponseReceived;
|
||||
job.ResponseDate = updatedJob.ResponseDate;
|
||||
job.Notes = updatedJob.Notes;
|
||||
job.CoverLetterText = updatedJob.CoverLetterText;
|
||||
job.JobUrl = updatedJob.JobUrl;
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
{
|
||||
"Version": "dailyexport.v1",
|
||||
"CreatedAt": "2026-03-25T02:00:00.0368687+01:00",
|
||||
"Companies": [
|
||||
{
|
||||
"Id": 1,
|
||||
"OwnerUserId": "23dc196b-f227-4499-93fe-403d8801e21c",
|
||||
"Name": "Acme Browser QA",
|
||||
"Location": null,
|
||||
"Source": null,
|
||||
"RecruiterName": "Maria Recruiter",
|
||||
"RecruiterEmail": "maria@acme.test",
|
||||
"RecruiterLinkedIn": null,
|
||||
"LastContactedAt": "2026-03-24T11:15:21.4772436",
|
||||
"NextContactAt": "2026-03-24T00:00:00",
|
||||
"PipelineStage": null
|
||||
}
|
||||
],
|
||||
"JobApplications": [
|
||||
{
|
||||
"Id": 1,
|
||||
"OwnerUserId": "23dc196b-f227-4499-93fe-403d8801e21c",
|
||||
"JobTitle": "Backend Developer",
|
||||
"CompanyId": 1,
|
||||
"Company": null,
|
||||
"Status": "Waiting",
|
||||
"DateApplied": "2026-03-01T13:00:00+01:00",
|
||||
"Location": null,
|
||||
"Salary": null,
|
||||
"NextAction": null,
|
||||
"FollowUpAt": "2026-03-24T00:00:00",
|
||||
"FeedbackRequestedAt": null,
|
||||
"RecruiterMessageDraft": "Saved browser recruiter message",
|
||||
"HasResume": true,
|
||||
"HasCoverLetter": true,
|
||||
"HasPortfolio": false,
|
||||
"HasOtherAttachment": false,
|
||||
"IsDeleted": false,
|
||||
"DeletedAt": null,
|
||||
"ResponseReceived": true,
|
||||
"ResponseDate": null,
|
||||
"Notes": "Browser-seeded notes\n\n\u003C\u003C\u003CAPPLICATION_ANSWER_DRAFT\u003E\u003E\u003E\nSaved browser application answer\n\u003C\u003C\u003CEND_APPLICATION_ANSWER_DRAFT\u003E\u003E\u003E",
|
||||
"CoverLetterText": "Saved browser cover letter",
|
||||
"JobUrl": "https://example.test/backend-developer",
|
||||
"Description": "Need .NET APIs and strong stakeholder communication.",
|
||||
"TranslatedDescription": null,
|
||||
"DescriptionLanguage": null,
|
||||
"Tags": "[\u0022.NET\u0022, \u0022APIs\u0022, \u0022Communication\u0022]",
|
||||
"Deadline": null,
|
||||
"ShortSummary": "Strong overlap in backend API delivery.",
|
||||
"TailoredCvText": "Saved browser tailored CV",
|
||||
"TailoredCvUpdatedAt": "2026-03-24T10:58:13.226164+01:00",
|
||||
"LastReminderEmailSentAt": null,
|
||||
"Messages": [],
|
||||
"Attachments": [],
|
||||
"Events": [],
|
||||
"DaysSince": 23
|
||||
}
|
||||
],
|
||||
"Correspondence": [
|
||||
{
|
||||
"Id": 1,
|
||||
"JobApplicationId": 1,
|
||||
"From": "Company",
|
||||
"Subject": "Backend Developer application update",
|
||||
"Channel": "Email",
|
||||
"ExternalMessageId": "browser-msg-1",
|
||||
"ExternalThreadId": "browser-thread-1",
|
||||
"ExternalFrom": "Maria Recruiter \u003Cmaria@acme.test\u003E",
|
||||
"ExternalTo": "admin@example.com",
|
||||
"Content": "We are aligning interview slots and need someone who can own the API layer.",
|
||||
"Date": "2026-03-10T10:00:00+01:00"
|
||||
},
|
||||
{
|
||||
"Id": 2,
|
||||
"JobApplicationId": 1,
|
||||
"From": "Me",
|
||||
"Subject": "Re: Backend Developer application update",
|
||||
"Channel": "Email",
|
||||
"ExternalMessageId": null,
|
||||
"ExternalThreadId": null,
|
||||
"ExternalFrom": null,
|
||||
"ExternalTo": null,
|
||||
"Content": "Hi Maria,\n\nEdited browser follow-up.\n\nThanks,\nadmin@example.com",
|
||||
"Date": "2026-03-24T11:15:21.4521755"
|
||||
}
|
||||
],
|
||||
"Attachments": [],
|
||||
"Events": [],
|
||||
"Rules": {
|
||||
"Id": 1,
|
||||
"AppliedFollowUpDays": 14,
|
||||
"AppliedGhostDays": 30,
|
||||
"OfferFollowUpDays": 7,
|
||||
"OfferGhostDays": 14,
|
||||
"FeedbackFollowUpDays": 7,
|
||||
"FeedbackGhostDays": 14
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
{
|
||||
"Version": "dailyexport.v1",
|
||||
"CreatedAt": "2026-03-26T02:00:00.005823+01:00",
|
||||
"Companies": [
|
||||
{
|
||||
"Id": 1,
|
||||
"OwnerUserId": "23dc196b-f227-4499-93fe-403d8801e21c",
|
||||
"Name": "Acme Browser QA",
|
||||
"Location": null,
|
||||
"Source": null,
|
||||
"RecruiterName": "Maria Recruiter",
|
||||
"RecruiterEmail": "maria@acme.test",
|
||||
"RecruiterLinkedIn": null,
|
||||
"LastContactedAt": "2026-03-24T11:15:21.4772436",
|
||||
"NextContactAt": "2026-03-24T00:00:00",
|
||||
"PipelineStage": null
|
||||
}
|
||||
],
|
||||
"JobApplications": [
|
||||
{
|
||||
"Id": 1,
|
||||
"OwnerUserId": "23dc196b-f227-4499-93fe-403d8801e21c",
|
||||
"JobTitle": "Backend Developer",
|
||||
"CompanyId": 1,
|
||||
"Company": null,
|
||||
"Status": "Waiting",
|
||||
"DateApplied": "2026-03-01T13:00:00+01:00",
|
||||
"Location": null,
|
||||
"Salary": null,
|
||||
"NextAction": null,
|
||||
"FollowUpAt": "2026-03-24T00:00:00",
|
||||
"FeedbackRequestedAt": null,
|
||||
"RecruiterMessageDraft": "Saved browser recruiter message",
|
||||
"HasResume": true,
|
||||
"HasCoverLetter": true,
|
||||
"HasPortfolio": false,
|
||||
"HasOtherAttachment": false,
|
||||
"IsDeleted": false,
|
||||
"DeletedAt": null,
|
||||
"ResponseReceived": true,
|
||||
"ResponseDate": null,
|
||||
"Notes": "Browser-seeded notes\n\n\u003C\u003C\u003CAPPLICATION_ANSWER_DRAFT\u003E\u003E\u003E\nSaved browser application answer\n\u003C\u003C\u003CEND_APPLICATION_ANSWER_DRAFT\u003E\u003E\u003E",
|
||||
"CoverLetterText": "Saved browser cover letter",
|
||||
"JobUrl": "https://example.test/backend-developer",
|
||||
"Description": "Need .NET APIs and strong stakeholder communication.",
|
||||
"TranslatedDescription": null,
|
||||
"DescriptionLanguage": null,
|
||||
"Tags": "[\u0022.NET\u0022, \u0022APIs\u0022, \u0022Communication\u0022]",
|
||||
"Deadline": null,
|
||||
"ShortSummary": "Strong overlap in backend API delivery.",
|
||||
"TailoredCvText": "Saved browser tailored CV",
|
||||
"TailoredCvUpdatedAt": "2026-03-24T10:58:13.226164+01:00",
|
||||
"LastReminderEmailSentAt": null,
|
||||
"Messages": [],
|
||||
"Attachments": [],
|
||||
"Events": [],
|
||||
"DaysSince": 24
|
||||
}
|
||||
],
|
||||
"Correspondence": [
|
||||
{
|
||||
"Id": 1,
|
||||
"JobApplicationId": 1,
|
||||
"From": "Company",
|
||||
"Subject": "Backend Developer application update",
|
||||
"Channel": "Email",
|
||||
"ExternalMessageId": "browser-msg-1",
|
||||
"ExternalThreadId": "browser-thread-1",
|
||||
"ExternalFrom": "Maria Recruiter \u003Cmaria@acme.test\u003E",
|
||||
"ExternalTo": "admin@example.com",
|
||||
"Content": "We are aligning interview slots and need someone who can own the API layer.",
|
||||
"Date": "2026-03-10T10:00:00+01:00"
|
||||
},
|
||||
{
|
||||
"Id": 2,
|
||||
"JobApplicationId": 1,
|
||||
"From": "Me",
|
||||
"Subject": "Re: Backend Developer application update",
|
||||
"Channel": "Email",
|
||||
"ExternalMessageId": null,
|
||||
"ExternalThreadId": null,
|
||||
"ExternalFrom": null,
|
||||
"ExternalTo": null,
|
||||
"Content": "Hi Maria,\n\nEdited browser follow-up.\n\nThanks,\nadmin@example.com",
|
||||
"Date": "2026-03-24T11:15:21.4521755"
|
||||
}
|
||||
],
|
||||
"Attachments": [],
|
||||
"Events": [],
|
||||
"Rules": {
|
||||
"Id": 1,
|
||||
"AppliedFollowUpDays": 14,
|
||||
"AppliedGhostDays": 30,
|
||||
"OfferFollowUpDays": 7,
|
||||
"OfferGhostDays": 14,
|
||||
"FeedbackFollowUpDays": 7,
|
||||
"FeedbackGhostDays": 14
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<key id="9a89a42c-d2bd-4770-83fb-5930685432db" version="1">
|
||||
<creationDate>2026-03-24T09:54:28.8487759Z</creationDate>
|
||||
<activationDate>2026-03-24T09:54:28.8487759Z</activationDate>
|
||||
<expirationDate>2026-06-22T09:54:28.8487759Z</expirationDate>
|
||||
<descriptor deserializerType="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=9.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
|
||||
<descriptor>
|
||||
<encryption algorithm="AES_256_CBC" />
|
||||
<validation algorithm="HMACSHA256" />
|
||||
<masterKey p4:requiresEncryption="true" xmlns:p4="http://schemas.asp.net/2015/03/dataProtection">
|
||||
<!-- Warning: the key below is in an unencrypted form. -->
|
||||
<value>LXbXqbpiEXn0OM6fr/TuXDBcZd83DvOInTI09PGZRr1Z20LQCD/PUKF1oo9UwC4O1VgK3wA//yxH9PPCIPzEaw==</value>
|
||||
</masterKey>
|
||||
</descriptor>
|
||||
</descriptor>
|
||||
</key>
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<key id="b3ca4672-1056-4ac2-ba47-0432608a4115" version="1">
|
||||
<creationDate>2026-03-27T07:52:25.0540436Z</creationDate>
|
||||
<activationDate>2026-03-27T07:52:25.0540436Z</activationDate>
|
||||
<expirationDate>2026-06-25T07:52:25.0540436Z</expirationDate>
|
||||
<descriptor deserializerType="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=9.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
|
||||
<descriptor>
|
||||
<encryption algorithm="AES_256_CBC" />
|
||||
<validation algorithm="HMACSHA256" />
|
||||
<masterKey p4:requiresEncryption="true" xmlns:p4="http://schemas.asp.net/2015/03/dataProtection">
|
||||
<!-- Warning: the key below is in an unencrypted form. -->
|
||||
<value>mfglwuKFrMSiWcbTVDEbPYM0eGAqlsOMHe89hNOsZUguUMMiusdx3m3ZQJvxnBCxeXte6OS+zvpZl3tIizvgHg==</value>
|
||||
</masterKey>
|
||||
</descriptor>
|
||||
</descriptor>
|
||||
</key>
|
||||
Reference in New Issue
Block a user