Skip to content

feat(hub): add GitHub authentication proof - #166

Open
alliscode wants to merge 17 commits into
build/hub/newfrom
alliscode-github-authentication-proof
Open

alliscode wants to merge 17 commits into
build/hub/newfrom
alliscode-github-authentication-proof

Conversation

@alliscode

Copy link
Copy Markdown
Collaborator

Why

Hub needs to prove who a person is before a later layer decides whether that person may use Hub. This adds a GitHub authentication proof flow while keeping identity proof separate from membership, admission, sessions, and product access.

What changes

Hub can now start and complete GitHub's OAuth authorization-code flow with state, PKCE, an exact callback URI, and a browser-binding cookie. GitHub's numeric user ID becomes the immutable external identity subject; login, name, avatar, and email remain display attributes.

A successful callback resolves the external identity through the identity kernel and returns a short-lived, opaque, one-use admission handoff. The handoff is explicitly pending admission and cannot be used as a Hub session. PostgreSQL enforces one-use transitions and uses database time for expiry. Database-backed start and callback rate limits work across Hub replicas, provider requests have fixed deadlines, and provider tokens are discarded after the current-user lookup without being persisted, logged, or audited.

This change does not issue a usable session, evaluate membership or admission, add OIDC or Entra support, grant repository permissions or product authority, publish prototypes, or change the UI.

Storybook screenshots

No Storybook screenshots are required because this change does not alter presentation or stories.

Ben Thomas and others added 17 commits October 30, 2026 14:14
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI auto review requested due to automatic review settings October 30, 2026 22:05
@alliscode
alliscode requested review from a team and nshki as code owners October 30, 2026 22:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved critical authentication flaws can permit expired proof consumption, misidentify users across GitHub instances, and expose callback code and state values in request logs.

14 open issues
What changed in this PR

Adds GitHub identity proof to Hub while keeping authentication separate from admission, sessions, and product access. Successful callbacks resolve an external identity and return a short-lived, browser-bound admission handoff.

Changes:

  • Adds OAuth start and callback routes with state, PKCE, browser binding, and provider request deadlines.
  • Adds database-backed one-use attempts, handoffs, rate limits, revocation state, and security audits.
  • Adds runtime configuration, documentation, and unit, migration, and concurrency test coverage.
File Description
hub/​test/​database/​reset-database/​index.ts Restores global revocation state after resets.
hub/​test/​database/​identity-foundation/​index.db.test.ts Adds authentication schema and constraint checks.
hub/​test/​database/​authentication-proof-migration/​index.db.test.ts Adds an identity-foundation upgrade test.
hub/​server/​utils/​http/​read-cookie/​index.ts Reads bounded cookie values.
hub/​server/​utils/​authentication/​parse-browser-binding/​index.ts Validates browser-binding values.
hub/​server/​utils/​authentication/​hash-authentication-value/​index.ts Hashes authentication artifacts.
hub/​server/​utils/​authentication/​encrypt-authentication-value/​index.ts Encrypts stored PKCE verifiers.
hub/​server/​utils/​authentication/​decrypt-authentication-value/​index.ts Decrypts stored PKCE verifiers.
hub/​server/​utils/​authentication/​create-opaque-value/​index.ts Generates random authentication values.
hub/​server/​run-api/​index.ts Initializes optional authentication before server startup.
hub/​server/​run-api/​index.test.ts Covers authentication startup ordering and failures.
hub/​server/​routes/​types.ts Adds authentication route dependencies.
hub/​server/​routes/​index.ts Registers configured authentication endpoints.
hub/​server/​routes/​api/​auth/​github/​types.ts Defines authentication route dependency types.
hub/​server/​routes/​api/​auth/​github/​start/​post/​index.ts Starts OAuth and sets browser binding.
hub/​server/​routes/​api/​auth/​github/​index.test.ts Covers route validation, responses, and logging.
hub/​server/​routes/​api/​auth/​github/​callback/​get/​index.ts Completes proof and returns a pending handoff.
hub/​server/​repositories/​security-audits/​parse-security-audit-event/​index.ts Validates authentication audit events.
hub/​server/​repositories/​security-audits/​get-security-audit-data/​index.ts Maps authentication events to audit records.
hub/​server/​repositories/​security-audits/​create-security-audit/​types.ts Adds typed authentication audit events.
hub/​server/​repositories/​security-audits/​create-security-audit/​index.test.ts Covers authentication audit mappings.
hub/​server/​repositories/​authorization-attempts/​delete-expired-authorization-attempts/​index.ts Deletes expired authorization attempts.
hub/​server/​repositories/​authorization-attempts/​create-authorization-attempt/​types.ts Defines authorization-attempt creation inputs.
hub/​server/​repositories/​authorization-attempts/​create-authorization-attempt/​index.ts Validates and stores authorization attempts.
hub/​server/​repositories/​authorization-attempts/​consume-authorization-attempt/​types.ts Defines attempt-consumption inputs.
hub/​server/​repositories/​authorization-attempts/​consume-authorization-attempt/​index.ts Implements one-use attempt consumption.
hub/​server/​repositories/​authentication-revocation-generations/​lock-authentication-revocation-generation/​index.ts Locks global revocation state.
hub/​server/​repositories/​authentication-revocation-generations/​increment-authentication-revocation-generation/​index.ts Advances global revocation state.
hub/​server/​repositories/​authentication-revocation-generations/​get-authentication-revocation-generation/​index.ts Reads the global revocation generation.
hub/​server/​repositories/​authentication-rate-limits/​reserve-authentication-rate-limit/​types.ts Defines rate-limit reservation inputs.
hub/​server/​repositories/​authentication-rate-limits/​reserve-authentication-rate-limit/​index.ts Reserves shared database-backed request capacity.
hub/​server/​repositories/​authentication-rate-limits/​reserve-authentication-rate-limit/​index.db.test.ts Covers rate-limit concurrency and rollback.
hub/​server/​repositories/​authentication-providers/​lock-authentication-provider-selection/​index.ts Locks provider selection during handoff decisions.
hub/​server/​repositories/​admission-handoffs/​find-admission-handoff/​index.ts Finds handoffs by token digest.
hub/​server/​repositories/​admission-handoffs/​delete-expired-admission-handoffs/​index.ts Deletes expired handoffs.
hub/​server/​repositories/​admission-handoffs/​create-admission-handoff/​types.ts Defines handoff persistence inputs.
hub/​server/​repositories/​admission-handoffs/​create-admission-handoff/​index.ts Validates and stores handoff evidence.
hub/​server/​repositories/​admission-handoffs/​consume-admission-handoff/​types.ts Defines handoff-consumption results.
hub/​server/​repositories/​admission-handoffs/​consume-admission-handoff/​index.ts Implements database-timed handoff consumption.
hub/​server/​integrations/​github/​errors.ts Adds a safe provider-request error.
hub/​server/​integrations/​github/​create-user-authentication-adapter/​types.ts Defines GitHub configuration and identity evidence.
hub/​server/​integrations/​github/​create-user-authentication-adapter/​index.ts Implements OAuth exchange and current-user lookup.
hub/​server/​integrations/​github/​create-user-authentication-adapter/​index.test.ts Covers OAuth arguments, identity mapping, and deadlines.
hub/​server/​integrations/​express/​read-trusted-proxy-hops/​index.ts Parses explicit proxy trust settings.
hub/​server/​integrations/​express/​read-trusted-proxy-hops/​index.test.ts Covers proxy-setting defaults and bounds.
hub/​server/​domains/​authentication/​types.ts Defines shared authentication contracts.
hub/​server/​domains/​authentication/​start-authentication-proof/​types.ts Defines proof-start dependencies and results.
hub/​server/​domains/​authentication/​start-authentication-proof/​index.ts Creates bound authorization attempts and provider URLs.
hub/​server/​domains/​authentication/​start-authentication-proof/​index.test.ts Covers attempt storage and revision denial.
hub/​server/​domains/​authentication/​run-admission-handoff-transaction/​types.ts Defines transaction-scoped handoff repositories.
hub/​server/​domains/​authentication/​run-admission-handoff-transaction/​index.ts Composes serializable handoff transactions.
hub/​server/​domains/​authentication/​record-authentication-denial/​index.ts Preserves denial and audit failures.
hub/​server/​domains/​authentication/​record-authentication-denial/​index.test.ts Covers denial audit outcomes.
hub/​server/​domains/​authentication/​read-github-authentication-configuration/​index.ts Parses explicit authentication configuration.
hub/​server/​domains/​authentication/​read-github-authentication-configuration/​index.test.ts Covers disabled, complete, and invalid configuration.
hub/​server/​domains/​authentication/​errors.ts Defines typed authentication failures.
hub/​server/​domains/​authentication/​create-github-authentication-services/​types.ts Defines service configuration inputs.
hub/​server/​domains/​authentication/​create-github-authentication-services/​index.ts Connects provider, identity, persistence, and audits.
hub/​server/​domains/​authentication/​create-admission-handoff/​index.ts Creates handoffs under revision and revocation locks.
hub/​server/​domains/​authentication/​create-admission-handoff/​index.test.ts Covers drift rejection and bounded retries.
hub/​server/​domains/​authentication/​consume-admission-handoff/​index.ts Validates and consumes admission handoffs.
hub/​server/​domains/​authentication/​complete-authentication-proof/​types.ts Defines proof-completion contracts.
hub/​server/​domains/​authentication/​complete-authentication-proof/​index.ts Resolves provider evidence into a pending handoff.
hub/​server/​domains/​authentication/​complete-authentication-proof/​index.test.ts Covers completion ordering and failure boundaries.
hub/​server/​domains/​authentication/​authentication-proof.db.test.ts Covers one-use, expiry, binding, and revocation behavior.
hub/​server/​domains/​authentication/​authentication-proof-races.db.test.ts Adds provider-selection and revocation race cases.
hub/​server/​create-app/​types.ts Adds authentication and proxy options.
hub/​server/​create-app/​index.ts Injects authentication services and proxy settings.
hub/​schema/​migrations/​20261030170000_authentication_proof/​migration.sql Adds authentication tables, constraints, and protection triggers.
hub/​schema/​external-identity.prisma Links external identities to handoffs.
hub/​schema/​authorization-attempt.prisma Models persisted authorization attempts.
hub/​schema/​authentication-revocation-generation.prisma Models global authentication revocation state.
hub/​schema/​authentication-rate-limit.prisma Models shared request-limit buckets.
hub/​schema/​authentication-provider-revision.prisma Links provider revisions to authentication artifacts.
hub/​schema/​admission-handoff.prisma Models browser-bound admission handoffs.
hub/​docs/​README.md Links the authentication proof guide.
hub/​docs/​development.md Documents local authentication setup.
hub/​docs/​database.md Describes the expanded identity schema.
hub/​docs/​authentication.md Documents proof flow, storage, and non-goals.
hub/​docs/​architecture.md Places authentication proof within Hub’s architecture.
hub/​.env.example Adds authentication and proxy configuration examples.

Review effort: Max (auto)

app.disable("x-powered-by")
app.set("trust proxy", trustedProxyHops)
app.use(createRequestLogger(logger))
app.use(express.json())
Comment on lines +56 to +60
function getRevision() {
return getSelectedProviderRevision(adapter.providerKey, {
findAuthenticationProvider: (key) => findAuthenticationProvider(prisma, key),
})
}

return {
provider: "github",
issuer: "https://github.com",
database: ConsumeAdmissionHandoffDatabase,
input: { browserBindingDigest: string; id: string },
): Promise<ConsumeAdmissionHandoffResult> {
const rows = await database.$queryRaw<Array<{ status: ConsumeAdmissionHandoffResult }>>`
Comment on lines +26 to +28
expiresAt: { gt: parsed.consumedAt },
},
data: { consumedAt: parsed.consumedAt },

const configurationSchema = z
.object({
appOrigin: z.url(),
Comment on lines +33 to +34
WHERE "window_started_at" + make_interval(secs => ${parsed.windowSeconds * 2})
<= clock_timestamp()
code: parsed.data.code,
state: parsed.data.state,
})
response.status(200).json({
@@ -0,0 +1,18 @@
import { createDecipheriv } from "node:crypto"

export function decryptAuthenticationValue(value: string, key: Buffer): string {
}
await promisify(execFile)(
process.execPath,
[prisma, "migrate", "resolve", "--applied", migrationName],
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.

2 participants