Skip to content

Conversation

@neSpecc
Copy link
Member

@neSpecc neSpecc commented Jan 25, 2026

This PR adds the initial GitHub App integration and basic support for a Task Manager based on GitHub Issues.

Key changes include:

  • GitHub App infrastructure: routes, services, and a Redis-backed installation state store for secure installs and webhooks.
  • Project-level Task Manager support (GitHub Issues), including new model fields and GraphQL types to link events with GitHub issues.
  • New GraphQL mutations to configure or disconnect the task manager for a project.
  • Safer updateProject logic with support for partial updates and field removal.
  • Updated dependencies required for GitHub integration.

This lays the foundation for future Task Manager features and deeper GitHub integration.

neSpecc and others added 12 commits January 18, 2026 20:27
Introduces a GitHubService class for interacting with the GitHub API, including methods for app installation, issue creation, and Copilot assignment. Adds related environment variables to .env.sample and type definitions to env.d.ts. Updates dependencies to include @octokit/rest, @octokit/types, and jsonwebtoken v9.0.3. Provides comprehensive tests for the new integration.
Introduces GraphQL types and mutations for managing Task Manager integration in projects, including disconnecting and updating settings. Updates the project model to support partial updates, extends resolvers for new mutations, and adds comprehensive tests for these features.
Introduces GitHub integration endpoints and service, including a Redis-backed state store for secure installation flow. Refactors integration code into modular files, appends GitHub routes to the Express app, and updates tests to import the service from its new location.
Refactored the /integration/github/connect endpoint to return a JSON object with the installation redirect URL instead of performing a direct redirect, and added colorized logging with environment-based suppression for tests. Updated Jest and argon2 dependencies, added Jest moduleNameMapper for node:crypto and node:util, and introduced mocks for these modules. Added comprehensive tests for the GitHub integration connect route.
Implemented /callback endpoint to handle GitHub App installation callbacks and save configuration to the project. Added /webhook endpoint to securely process GitHub webhook events, including removal of taskManager config on installation deletion. Improved logging with project context and added utility functions for URL building and signature verification.
Introduces endpoints to list and update GitHub repositories for a project, adds a Repository type to the GitHub service, and refactors project admin access validation. Also adds the TaskManagerItem GraphQL type and links it to the Event type for improved integration with task managers like GitHub Issues.
Introduces GitHub OAuth flow for user-to-server tokens, including endpoints for handling OAuth callbacks and exchanging codes for tokens. Updates the GitHub service to support OAuth code exchange, token validation, and refresh, and adds required environment variables for client ID and secret. Updates dependencies to support new OAuth methods and expands the project model to store task manager configuration.
Updated the assignCopilot method to use the user-to-server OAuth token and GitHub GraphQL API for assigning the Copilot agent to issues. Improved error handling, added detailed logging, and ensured compatibility with the Copilot bot assignment process. Also added input validation for installationId in relevant methods.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces GitHub App integration for a Task Manager feature based on GitHub Issues. The implementation includes OAuth flows, webhook handling, GraphQL API extensions, and Redis-backed state management.

Changes:

  • Added GitHub App integration infrastructure with secure OAuth flows, webhook handling (including HMAC signature verification), and Redis-backed installation state store
  • Extended GraphQL schema with Task Manager types, mutations, and event fields to support GitHub Issue linking
  • Updated project model to support partial updates and added taskManager configuration field
  • Upgraded test dependencies (Jest 26→30, ts-jest, @types/jest) and updated test files to remove deprecated done callbacks

Reviewed changes

Copilot reviewed 26 out of 29 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/integrations/github/service.ts Core GitHub API service with JWT/OAuth authentication, issue creation, and Copilot assignment
src/integrations/github/routes.ts Express routes for OAuth flows, webhooks, and repository management
src/integrations/github/store/install-state.redis.store.ts Redis-backed store for OAuth state with atomic operations
src/resolvers/project.js New mutations for connecting/disconnecting Task Manager and updating settings
src/models/project.ts Updated updateProject to support partial updates
src/typeDefs/*.ts GraphQL type definitions for Task Manager, settings, and event integration
package.json Dependency updates including Octokit libraries, Jest 30, argon2, and jsonwebtoken
test/resolvers/project.test.ts Comprehensive tests for Task Manager mutations
test/integrations/github*.test.ts Tests for GitHub service and routes
.env.sample Sample configuration for GitHub App credentials

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

"amqplib": "^0.5.5",
"apollo-server-express": "^3.10.0",
"argon2": "^0.28.7",
"argon2": "^0.44.0",
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

The upgrade from argon2 ^0.28.7 to ^0.44.0 is a significant version jump. While argon2 is a stable library, this jump spans many major versions and could introduce breaking changes. Verify that:

  1. The API remains compatible with existing password hashing/verification code
  2. All existing password hashes can still be verified with the new version
  3. The native bindings compile correctly in your deployment environment

This is particularly important since argon2 is a native module and version mismatches can cause runtime errors.

Suggested change
"argon2": "^0.44.0",
"argon2": "^0.28.7",

Copilot uses AI. Check for mistakes.
Comment on lines +437 to +473
const repoInfoQuery = `
query($owner: String!, $name: String!) {
repository(owner: $owner, name: $name) {
id
issue(number: ${issueNumber}) {
id
}
suggestedActors(capabilities: [CAN_BE_ASSIGNED], first: 100) {
nodes {
login
__typename
... on Bot {
id
}
... on User {
id
}
}
}
}
}
`;

type RepoInfoGraphQLResponse = {
repository?: {
id: string;
issue?: { id: string };
suggestedActors: {
nodes: Array<{ login: string; __typename?: string; id?: string }>;
};
};
};

const repoInfo = await octokit.graphql<RepoInfoGraphQLResponse>(repoInfoQuery, {
owner,
name: repo,
});
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

The GraphQL query embeds the issueNumber directly into the query string using template literals (line 441). This creates a potential GraphQL injection vulnerability if issueNumber is not properly validated. Although issueNumber is typed as a number in the function signature, it should be passed as a GraphQL variable instead of being interpolated into the query string.

Change the query to use variables:

const repoInfoQuery = `
  query($owner: String!, $name: String!, $issueNumber: Int!) {
    repository(owner: $owner, name: $name) {
      id
      issue(number: $issueNumber) {
        id
      }
      // ... rest of query
    }
  }
`;

const repoInfo = await octokit.graphql<RepoInfoGraphQLResponse>(repoInfoQuery, {
  owner,
  name: repo,
  issueNumber,
});

This is a security best practice to prevent injection attacks.

Copilot uses AI. Check for mistakes.
Comment on lines +376 to +385
await project.updateProject({
taskManager: project.taskManager ? {
...project.taskManager,
...taskManagerConfig,
config: {
...project.taskManager.config,
// eslint-disable-next-line @typescript-eslint/camelcase, camelcase
installationId: installation_id,
},
} : taskManagerConfig,
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

When updating an existing taskManager configuration (lines 377-385), the code spreads the existing project.taskManager.config but then only updates the installationId. This could potentially lose other config properties like delegatedUser if they exist. The safer approach would be:

taskManager: project.taskManager ? {
  ...project.taskManager,
  autoTaskEnabled: false,
  taskThresholdTotalCount: DEFAULT_TASK_THRESHOLD_TOTAL_COUNT,
  assignAgent: false,
  updatedAt: new Date(),
  config: {
    ...project.taskManager.config,
    installationId: installation_id,
  },
} : taskManagerConfig

This preserves all existing config properties while only updating the necessary fields.

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +24
import { ProjectDBScheme } from '@hawk.so/types';

/**
* Task Manager configuration type (matching ProjectTaskManagerConfig from @hawk.so/types)
*/
type ProjectTaskManagerConfig = {
type: 'github';
autoTaskEnabled: boolean;
taskThresholdTotalCount: number;
assignAgent: boolean;
usage?: {
dayStartUtc: Date;
autoTasksCreated: number;
};
connectedAt: Date;
updatedAt: Date;
config: {
installationId: string;
repoId: string | number;
repoFullName: string;
};
};
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

The test file duplicates the ProjectTaskManagerConfig type definition (lines 8-24) instead of importing it from @hawk.so/types. This creates a maintenance burden as changes to the actual type won't be reflected in tests. Since the comment on line 6 mentions "matching ProjectTaskManagerConfig from @hawk.so/types", this type should be imported directly:

import { ProjectDBScheme, ProjectTaskManagerConfig } from '@hawk.so/types';

Then remove the duplicate type definition.

Suggested change
import { ProjectDBScheme } from '@hawk.so/types';
/**
* Task Manager configuration type (matching ProjectTaskManagerConfig from @hawk.so/types)
*/
type ProjectTaskManagerConfig = {
type: 'github';
autoTaskEnabled: boolean;
taskThresholdTotalCount: number;
assignAgent: boolean;
usage?: {
dayStartUtc: Date;
autoTasksCreated: number;
};
connectedAt: Date;
updatedAt: Date;
config: {
installationId: string;
repoId: string | number;
repoFullName: string;
};
};
import { ProjectDBScheme, ProjectTaskManagerConfig } from '@hawk.so/types';

Copilot uses AI. Check for mistakes.
Comment on lines +544 to +559
/**
* Use timing-safe comparison to prevent timing attacks
*/
let signatureValid = false;

if (signature.length === calculatedSignature.length) {
let match = true;

for (let i = 0; i < signature.length; i++) {
if (signature[i] !== calculatedSignature[i]) {
match = false;
}
}

signatureValid = match;
}
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

The manual timing-safe comparison implementation is functionally correct but Node.js provides a built-in crypto.timingSafeEqual() function that is specifically designed for this purpose and is more robust. Consider replacing this manual implementation with:

const crypto = require('crypto');
// ... after calculating both signatures
const signatureValid = crypto.timingSafeEqual(
  Buffer.from(signature),
  Buffer.from(calculatedSignature)
);

This is the recommended approach for comparing security-sensitive values.

Copilot uses AI. Check for mistakes.
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