Skip to content

Local hosting - #97

Merged
BenceKovari merged 20 commits into
masterfrom
local-hosting
Aug 9, 2026
Merged

Local hosting#97
BenceKovari merged 20 commits into
masterfrom
local-hosting

Conversation

@BenceKovari

Copy link
Copy Markdown
Contributor

No description provided.

BenceKovari and others added 20 commits July 20, 2026 17:16
- Implement health check page for site administration with detailed reports on course integrations.
- Create user management interface allowing admins to add, edit, and delete users, as well as manage course assignments.
- Introduce no-access page for users without course assignments.
- Develop shared health chain component to visualize integration statuses.
- Add corresponding styles for new components and pages.
- Implemented assignments.html for displaying and managing assignments.
- Created assignments.scss for styling the assignments page.
- Developed assignments.ts to handle assignment logic, including creation, editing, and loading assignments.
- Added invite.html for students to accept assignments via invite links.
- Created invite.scss for styling the invite acceptance page.
- Developed invite.ts to manage the invite acceptance flow and GitHub username input.
- Implemented my-assignments.html to display a student's repositories across courses.
- Created my-assignments.scss for styling the student's assignments page.
- Developed my-assignments.ts to handle loading and resending invitations for student repositories.
….config for server configuration

- Created initial migration for database schema including tables for users, roles, courses, assignments, and related entities.
- Implemented unit tests for user Neptun code validation in UsersAdminController, ensuring uniqueness and proper handling of blank values.
- Added web.config to configure ASP.NET Core hosting settings and error handling.
- Implemented CheckTemplate endpoint in AssignmentsController for real-time validation of template repository names during assignment creation.
- Added CheckTemplateRequest DTO to facilitate the request structure.
- Updated AssignmentsClient to include checkTemplate method for API interaction.
- Enhanced CourseAssignments component to verify template status after assignment save, displaying any issues to the user.
- Introduced a new GitHub setup help page for guiding users through GitHub App registration and configuration.
- Improved UI in course editor and user management sections, including password generation and token management features.
Adds Octokit to Ahk.Web.Services and introduces ICourseGitHubClientFactory as
the single place the portal builds an authenticated GitHub client, ahead of
porting github-monitor's handlers (which are written against Octokit's payload
types and API surface).

GitHubRepositoryService is rewritten on top of it. The interface and its four
record projections are deliberately unchanged: they keep Octokit's models out of
the domain, and they are what lets AssignmentInviteTests mock the service
strictly - that suite is the regression net for this rewrite and still passes
untouched.

The named "github" HttpClient stays, but only for CourseGitHubAppTokenProvider's
App-JWT bootstrap and the health checks. Converting the token provider would
change the shape of the permissions dictionary GitHubAppInstallationHealthCheck
reads, for no gain.

Two Octokit 14 details worth recording: NewRepositoryFromTemplate has no
IncludeAllBranches property (GitHub defaults it to false, which is what we want),
and Repository.IsTemplate / RepositoryInvitation.Expired are non-nullable bool.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ports github-monitor's webhook receiver and all ten of its handlers into the
portal, so the reserved path stops answering 405 from the SPA fallback. The six
Azure Storage queues are gone with it: the status-tracking handlers call
ISubmissionEventService and the chatops handlers call IGradeService in-process,
both of which already existed with no callers.

Structural changes from the original, everything else verbatim (the bilingual
warning strings are a student-facing interface):

- Handlers are scoped DI registrations behind IGitHubWebhookHandler rather than
  reflectively activated types. ActivatorUtilities only ever existed to hand the
  Function's per-invocation ILogger to a handler. Registration order is the
  dispatch order and is kept identical.
- Per-delivery state (course, delivery id, body, client, run threshold) moved
  onto GitHubWebhookContext, so handlers hold none.
- ActionWorkflowRunHandler's threshold was a const 5 because a deployment served
  one course; it now reads CourseGitHubConfig.WorkflowRunThreshold.
- The workflow payload types move out of `namespace Octokit` into our own. The
  day Octokit ships its own WorkflowRunEventPayload, that trick stops the build
  for reasons nobody would connect to this file.

The load-bearing design point is that the webhook secret is per course, and the
only thing identifying the course is repository.full_name inside the body - so an
untrusted body must be parsed before it can be verified. Everything before the
signature check is kept inert: one property read from a JsonDocument that is then
dropped, two indexed reads, no writes, no GitHub calls, no body logging. A forged
name just selects a different course's secret and fails the same 400.

Benign cases (repo in no course, integration off) answer 202, not 4xx: GitHub
colours non-2xx red in the delivery log, and during cutover an organization
legitimately contains repositories that are not a course.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ation-result

Ports grade-management's evaluation-result function, the last of the three
write-side entry points. HmacSha256Validator, the DTO and the validation order
are verbatim ports: publish-results-pr is a Go client running inside student
repositories that are updated on their own schedule, so this is a wire contract,
and the error strings end up in student build logs.

Two things deliberately left as they are rather than "fixed":

- Unknown JSON members stay tolerated. The Go client sends imageFiles, which has
  never had a counterpart here.
- [Required] on the nested taskName is not actually enforced, because
  Validator.TryValidateObject does not recurse into collections. Enforcing it
  would start failing student builds that pass today.

New in the portal: the course comes from the callback token - the authenticated
credential - not from the caller-supplied repository name. A mismatch between the
two is logged as a warning rather than rejected, since it means a course is using
another course's token and the grade is about to land in the wrong place.

IDateTimeProvider is replaced by TimeProvider, registered in Program.cs, so the
ten-minute clock-skew window stays testable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
201 tests pass, up from 81. Also hardens two things the port inherited.

Parity suites, ported case-for-case from the Azure Functions so the portal
provably accepts what they accepted: the ahk-monitor.yml grammar, the /ahk ok
grammar (including that the last matching line wins - the parse loop's missing
break is behaviour, not a bug), and the X-Hub-Signature-256 vectors.

HmacSha256ValidatorTests carries the three golden vectors that exist identically
in grade-management's suite AND in publish-results-pr's Go tests. Reproducing
those base64 strings is the proof the portal is wire-compatible with evaluator
containers already running in student repositories, which cannot be redeployed
on our schedule.

WebhookHandlerRegistrationTests turns the delivery-id invariant into a build
failure: GitHubDeliveryId is globally unique but a delivery fans out to several
handlers, so at most one handler per event may write a status event. Break that
and rows vanish into the redelivery guard - in production, silently.

Endpoint tests pin the full response contract of both routes, including that
neither returns 401 (proving [AllowAnonymous] works because Program.cs has no
fallback policy), and that a body signed with another course's secret is
indistinguishable from any other bad signature - which is what contains the
parse-before-verify ordering.

Two security fixes from review of the previous commits, neither changing which
requests are accepted:

- Both validators compared signatures with string.Equals, which returns at the
  first differing character and lets a forged signature be refined a character
  at a time by timing. Now CryptographicOperations.FixedTimeEquals.
- The CI callback logged the caller's token in full at Information level, as
  grade-management did, putting working credentials into shipped logs. Now
  masked to a last-four hint, matching the admin API's convention.

Octokit models in tests are built by deserializing the JSON GitHub would send
rather than through their constructors, which are long and mostly irrelevant.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
github-app.md was actively misleading: it told administrators to point the App's
webhook at github-monitor because the portal's receiver did not exist. That
instruction is what produced the 405s this work started from. It now documents
the real URL, that the webhook secret is per course, the five subscribed events,
and a per-course cutover checklist.

New: a "Delivery responses" table explaining what each status means when read in
Advanced -> Recent Deliveries, since that page is the only diagnostic surface
GitHub offers. Troubleshooting gains the three symptoms actually hit while
debugging - 405 with Allow: GET, HEAD (URL points at the SPA fallback), 200 with
nothing happening (repo not opted in), and no deliveries at all (installation
limited to selected repositories).

New docs/ci-callback.md covers the HMAC contract end to end. It leads with the
fact that the URL is signed, because that is the failure mode operators will
actually meet, and http:// fails via a 307 and then a scheme mismatch - which
looks nothing like a URL problem.

The permissions table no longer marks half the grants "github-monitor only";
the portal needs them now. Contents moves to read/write - it reads
ahk-monitor.yml and neptun.txt out of every student repository.

CLAUDE.md records the non-obvious constraints a future change could quietly
break: the one-status-event-per-delivery invariant, why course resolution has to
precede signature validation and what must stay out of that stretch, the
12-hour ahk-monitor.yml cache, the GetForCourseAsync(Course) null trap, and that
a GitHub App has one webhook URL so cutover is a flip rather than a parallel run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Five additions, each one something that cost a build or a test run:

- How to probe a NuGet package's API surface here, and that the XML doc file is
  partial so absence from it proves nothing. Plus the two Octokit 14 facts found
  that way (no IncludeAllBranches; IsTemplate/Expired are non-nullable).
- A curl + openssl recipe for exercising the webhook and CI callback by hand,
  with the dev seeder's org and secret. This is the fastest path for the next
  person debugging a delivery, and viaubb01 having no secret on purpose makes it
  a ready-made test of the failure branch.
- That `git add -A` at the repo root stages .claude/settings.json, which shares
  permission grants with everyone who clones. Learned by doing it.
- Test patterns for the m2m endpoints: build Octokit models by deserializing
  GitHub's JSON rather than through their constructors, which of the DI
  registrations WebApplicationFactory has to replace, and two MSTest-to-xUnit
  traps (no params in InlineData; HttpClient refuses to send a malformed Date).
- That both signature comparisons must stay constant-time. The Azure Functions
  use string.Equals, so a future "restore parity" instinct would quietly undo a
  security fix - worth stating rather than leaving to a code comment.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@BenceKovari
BenceKovari merged commit 24788b7 into master Aug 9, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant