Skip to main content
DeployHub integrates deeply with GitHub to provide seamless authentication and repository access for both public and private repositories.

GitHub OAuth Flow

DeployHub uses GitHub OAuth 2.0 for secure authentication and repository access.

Authentication Scopes

// From githubLogin.js:2
const redirectUrl = `https://github.com/login/oauth/authorize?client_id=${process.env.GITHUB_CLIENT_ID}&scope=repo user:email admin:repo_hook`;
Requested Scopes:
  • repo - Full access to public and private repositories
  • user:email - Read user email addresses
  • admin:repo_hook - Create and manage webhooks for auto-deployments
The admin:repo_hook scope enables automatic redeployment when you push changes to GitHub.

Login Endpoint

GET /api/auth/github/login
Redirects to GitHub’s authorization page where users grant permissions.

Callback Handler

After authorization, GitHub redirects back to DeployHub’s callback:
// From githubCallback.js:6-29
export const githubCallback = async (req, res) => {
  const { code } = req.query;

  // 1. Exchange code → access_token
  const tokenRes = await axios.post(
    "https://github.com/login/oauth/access_token",
    {
      client_id: process.env.GITHUB_CLIENT_ID,
      client_secret: process.env.GITHUB_CLIENT_SECRET,
      code,
    },
    { headers: { Accept: "application/json" } }
  );

  const githubAccessToken = tokenRes.data.access_token;

  // 2. Get GitHub Profile
  const userRes = await axios.get("https://api.github.com/user", {
    headers: {
      Authorization: `Bearer ${githubAccessToken}`,
    },
  });

  const githubUser = userRes.data;
  // ...
};

User Profile Creation

DeployHub creates or updates your user profile with GitHub data:
// From githubCallback.js:40-75
const emailRes = await axios.get("https://api.github.com/user/emails", {
  headers: {
    Authorization: `Bearer ${githubAccessToken}`,
  },
});

const primaryEmail = emailRes.data.find(e => e.primary)?.email;

let user = await Model.User.findOne({ email: primaryEmail });

if (!user) {
  user = await Model.User.create({
    fullname: githubUser.name || githubUser.login,
    email: primaryEmail,
    githubId: githubUser.id,
    githubUsername: githubUser.login,
    githubAccessToken,
    provider: "github",
    password: null,
  });
} else {
  // Update GitHub token
  user.githubAccessToken = githubAccessToken;
  user.githubId = githubUser.id;
  user.githubUsername = githubUser.login;
  user.provider = "github";
  await user.save({ validateBeforeSave: false });
}
If you already have an account with the same email, DeployHub links your GitHub account to it.

Repository Access

Fetching User Repositories

DeployHub retrieves all repositories you have access to:
// From getUserRepo.controller.js:4-26
export const getUserRepos = async (req, res) => {
  if (req.user.provider !== "github") {
    return res.status(400).json({
      message: "You are not logged in with github",
    });
  }
  
  const githubAccessToken = req.user.githubAccessToken;
  
  // Fetch repos from GitHub
  const reposRes = await axios.get("https://api.github.com/user/repos", {
    headers: {
      Authorization: `Bearer ${githubAccessToken}`,
      Accept: "application/vnd.github+json",
    },
    params: {
      visibility: "all",
      affiliation: "owner,collaborator",
      per_page: 100,
    },
  });

  const repos = reposRes.data.map((repo) => ({
    id: repo.id,
    name: repo.name,
    full_name: repo.full_name,
    private: repo.private,
    html_url: repo.html_url,
    description: repo.description,
    default_branch: repo.default_branch,
  }));

  return res.json({ repos });
};
API Endpoint:
GET /api/git/repos
Response:
{
  "repos": [
    {
      "id": 123456789,
      "name": "my-app",
      "full_name": "username/my-app",
      "private": false,
      "html_url": "https://github.com/username/my-app",
      "description": "My awesome application",
      "default_branch": "main"
    }
  ]
}

Private Repository Cloning

DeployHub uses stored GitHub access tokens to clone private repositories:
// From buildworker.js:190-197
let repoUrlWithAuth;

if (usergithubAccessToken) {
  repoUrlWithAuth = `https://${usergithubAccessToken}@github.com/${owner}/${repo}.git`;
} else {
  repoUrlWithAuth = `https://github.com/${owner}/${repo}.git`;
}

execSync(`git clone -b ${branchname} ${repoUrlWithAuth} ${buildFilePath}`, {
  stdio: "inherit",
});
If you’re not authenticated with GitHub, DeployHub can still clone public repositories.

Commit SHA Tracking

DeployHub fetches the latest commit SHA to detect changes:
// From createDeployment.controller.js:107-126
try {
  let headers = {
    "Accept": "application/vnd.github.v3+json"
  };

  if (user.githubAccessToken) {
    headers.Authorization = `token ${user.githubAccessToken}`;
  }

  const response = await fetch(
    `https://api.github.com/repos/${owner}/${repo}/git/ref/heads/${branchname}`,
    { headers }
  );

  const data = await response.json();
  commitSha = data?.object?.sha || null;
  console.log("Commit SHA:", commitSha);
} catch (err) {
  console.log("Commit fetch failed, continuing without commit check");
}
Build Model:
const newBuild = new Model.Build({
  project: newProject._id,
  commitSha: commitSha,
});
Commit SHA tracking enables intelligent redeployments - DeployHub only rebuilds when code changes.

Automatic Webhooks

After successful deployment, DeployHub automatically creates a GitHub webhook for continuous deployment:
// From deployworker.js:107-139
if (usergithubAccessToken) {
  const result = await fetch(
    `https://api.github.com/repos/${owner}/${repo}/hooks`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${usergithubAccessToken}`,
        Accept: "application/vnd.github+json"
      },
      body: JSON.stringify({
        name: "web",
        active: true,
        events: ["push"],
        config: {
          url: process.env.NODE_ENV === 'production' 
            ? "https://api.deployhub.cloud/github-webhook" 
            : "https://b960-103-211-132-79.ngrok-free.app/github-webhook",
          content_type: "json",
          secret: process.env.GITHUB_WEBHOOK_SECRET
        }
      })
    }
  );
}
Webhook Configuration:
  • Event: push (triggers on code push)
  • Payload URL: DeployHub webhook endpoint
  • Content Type: JSON
  • Secret: HMAC signature verification

Webhook Handler

The webhook handler processes GitHub push events:
// From git_webHookRoutes.js:8-78
router.post(
  "/",
  express.raw({ type: "*/*" }),
  async (req, res) => {
    // 1. Verify HMAC signature
    const signature = req.headers["x-hub-signature-256"];
    const secret = process.env.GITHUB_WEBHOOK_SECRET;
    
    const hmac = crypto.createHmac("sha256", secret);
    const digest = "sha256=" + hmac.update(req.body).digest("hex");
    
    if (!crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(digest)
    )) {
      return res.status(401).send("Invalid signature");
    }

    const payload = JSON.parse(req.body.toString());

    // 2. Only push events on default branch
    if (req.headers["x-github-event"] !== "push") {
      return res.send("Ignored (not push event)");
    }

    const branch = payload.ref.replace("refs/heads/", "");
    const defaultBranch = payload.repository.default_branch;

    if (branch !== defaultBranch) {
      return res.send("Ignored (not default branch)");
    }

    // 3. Find and redeploy matching projects
    const repoUrl = payload.repository.clone_url.replace(/\.git$/, "");
    const projects = await Model.Project.find({ 
      repoLink: repoUrl, 
      status: { $ne: 'deleted' } 
    }).populate('owner');

    for (const project of projects) {
      if (project.owner.githubAccessToken) {
        project.status = "building";
        await project.save({ validateBeforeSave: false });

        await reDeploymentQueue.add("redeployment", project._id);
        console.log("Auto redeploy triggered for", project._id);
      }
    }

    res.status(200).send("Redeploy triggered for eligible projects");
  }
);
Webhooks are only created for projects deployed by authenticated GitHub users. Public deployments won’t receive automatic updates.

Security Features

HMAC Signature Verification

All webhook requests are verified using HMAC-SHA256:
const hmac = crypto.createHmac("sha256", secret);
const digest = "sha256=" + hmac.update(req.body).digest("hex");

if (!crypto.timingSafeEqual(
  Buffer.from(signature),
  Buffer.from(digest)
)) {
  return res.status(401).send("Invalid signature");
}

Token Storage

GitHub access tokens are securely stored in the database:
// User model includes:
{
  githubId: Number,
  githubUsername: String,
  githubAccessToken: String,  // Encrypted in production
  provider: "github"
}
Access tokens are never exposed to the frontend or in API responses.

Branch Selection

You can deploy from any branch in your repository:
// Project settings include:
{
  settings: {
    repoBranchName: {
      type: String,
      default: "main"
    }
  }
}
Update branch in project settings to deploy from a different branch.

Limitations

  • Maximum 100 repositories per request (paginated)
  • Webhooks created only on first deployment
  • Branch must exist when creating deployment
  • Token expires if you revoke GitHub access

Troubleshooting

”GitHub token missing”

You need to log in with GitHub to access private repositories:
GET /api/auth/github/login

“You are not logged in with github”

Your account was created with email/password. Connect GitHub from settings or create a new account.

Webhook not triggering

Check that:
  1. You deployed the project while authenticated with GitHub
  2. The webhook exists in your repository settings
  3. Your GitHub token hasn’t been revoked
  4. Pushes are to the default branch configured in settings