Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 26 additions & 5 deletions backend/services/authService.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,25 @@ const { Octokit } = require('@octokit/rest');

const GOOGLE_NOT_AVAILABLE = 'Google auth is not available until Phase 3';

/** Current supported REST calendar version — avoids 2022-11-28 deprecation noise. */
const GITHUB_API_VERSION = '2026-03-10';

/**
* Build an Octokit client that always sends X-GitHub-Api-Version.
* Constructor `request.headers` is ignored by @octokit/rest@20 — must use defaults.
*
* @param {object} [options]
* @param {string} [options.auth]
* @returns {import('@octokit/rest').Octokit}
*/
function createGitHubOctokit(options = {}) {
const octokit = new Octokit(options);
octokit.request = octokit.request.defaults({
headers: { 'X-GitHub-Api-Version': GITHUB_API_VERSION },
});
return octokit;
}

class AuthService {
/**
* @param {object} [deps]
Expand Down Expand Up @@ -116,7 +135,7 @@ class AuthService {

if (this._hasGitHubAppSigningCredentials()) {
try {
const appOctokit = new Octokit({ auth: this._createAppJwt() });
const appOctokit = createGitHubOctokit({ auth: this._createAppJwt() });
const { data } = await appOctokit.rest.apps.getRepoInstallation({
owner,
repo,
Expand Down Expand Up @@ -160,7 +179,7 @@ class AuthService {
if (!this._hasGitHubAppSigningCredentials()) {
return installUrl;
}
const appOctokit = new Octokit({ auth: this._createAppJwt() });
const appOctokit = createGitHubOctokit({ auth: this._createAppJwt() });
const { data } = await appOctokit.rest.apps.getAuthenticated();
if (data?.slug) {
installUrl = `https://github.com/apps/${data.slug}/installations/new`;
Expand Down Expand Up @@ -249,12 +268,12 @@ class AuthService {
throw new Error(await this._installationSetupMessage(fullName));
}

const appOctokit = new Octokit({ auth: this._createAppJwt() });
const appOctokit = createGitHubOctokit({ auth: this._createAppJwt() });
const { data } = await appOctokit.rest.apps.createInstallationAccessToken({
installation_id: installationId,
});

return new Octokit({ auth: data.token });
return createGitHubOctokit({ auth: data.token });
}

/** @param {string | undefined} b64 */
Expand Down Expand Up @@ -437,7 +456,7 @@ class AuthService {
}

const accessToken = this.decrypt(encrypted);
return new Octokit({ auth: accessToken });
return createGitHubOctokit({ auth: accessToken });
}

/**
Expand Down Expand Up @@ -538,3 +557,5 @@ class AuthService {
}

module.exports = AuthService;
module.exports.createGitHubOctokit = createGitHubOctokit;
module.exports.GITHUB_API_VERSION = GITHUB_API_VERSION;
29 changes: 29 additions & 0 deletions backend/tests/authService.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,35 @@ test('getGitHubClient returns authenticated Octokit', () => {
assert.equal(typeof client.rest.repos.listForAuthenticatedUser, 'function');
});

test('createGitHubOctokit sends X-GitHub-Api-Version on requests', async () => {
const { createGitHubOctokit, GITHUB_API_VERSION } = require('../services/authService');
/** @type {Headers | Record<string, string> | undefined} */
let capturedHeaders;

const client = createGitHubOctokit({
auth: 'gho_test_token',
request: {
fetch: async (_url, options = {}) => {
capturedHeaders = options.headers;
return new Response(JSON.stringify({ login: 'sam' }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
},
},
});

await client.rest.users.getAuthenticated();

assert.ok(capturedHeaders);
const headers =
typeof capturedHeaders.get === 'function'
? capturedHeaders
: new Headers(capturedHeaders);
assert.equal(headers.get('X-GitHub-Api-Version'), GITHUB_API_VERSION);
assert.equal(GITHUB_API_VERSION, '2026-03-10');
});

test('getGoogleDriveClient returns null until Phase 3', () => {
const authService = new AuthService({
sessionSecret: TEST_SESSION_SECRET,
Expand Down