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/
|
||||||
/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
|
# Local app data
|
||||||
*.db
|
*.db
|
||||||
*.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