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:
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:
- You deployed the project while authenticated with GitHub
- The webhook exists in your repository settings
- Your GitHub token hasn’t been revoked
- Pushes are to the default branch configured in settings