Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions .gitleaksignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/github/workspace/src/auth.ts:generic-api-key:7
62 changes: 62 additions & 0 deletions __tests__/auth.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {
authenticate,
GITHUB_AUTH_API_KEY,
GITHUB_AUTH_SERVICE_URL
} from '../src/auth'
import { jest } from '@jest/globals'

describe('authenticate', () => {
const mockFetch = jest.spyOn(global, 'fetch')

beforeEach(() => {
mockFetch.mockClear()
})

it('should use GitHub App authentication when app is installed', async () => {
mockFetch.mockResolvedValue({
ok: true,
status: 200,
json: async () => Promise.resolve({ token: 'ghs_app_123' })
} as Response)

const result = await authenticate(
{ owner: 'dunder-mifflin', repo: 'website' },
'fallback_token'
)

expect(result).toBe('ghs_app_123')
expect(mockFetch).toHaveBeenCalledWith(
`${GITHUB_AUTH_SERVICE_URL}/github/dunder-mifflin/website/installation-token`,
expect.objectContaining({
method: 'POST',
headers: { Authorization: `Bearer ${GITHUB_AUTH_API_KEY}` }
})
)
})

it('should fall back to standard authentication when app is not installed', async () => {
mockFetch.mockResolvedValue({
ok: false,
status: 404,
json: async () => Promise.resolve({ error: 'Not installed' })
} as Response)

const result = await authenticate(
{ owner: 'dunder-mifflin', repo: 'website' },
'fallback_token'
)

expect(result).toBe('fallback_token')
})

it('should fall back to standard authentication when service is unavailable', async () => {
mockFetch.mockRejectedValue(new Error('Network error'))

const result = await authenticate(
{ owner: 'dunder-mifflin', repo: 'website' },
'fallback_token'
)

expect(result).toBe('fallback_token')
})
})
55 changes: 52 additions & 3 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

66 changes: 66 additions & 0 deletions src/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import * as core from '@actions/core'
import type { Context } from '@actions/github/lib/context'

export const GITHUB_AUTH_SERVICE_URL =
'https://github-auth.staging.code-pushup.dev'

export const GITHUB_AUTH_API_KEY =
'18850f2513adad10662e85f4f085a9714e64cef7793fc2ffe903b5ddcd62de42'

type TokenResponse = {
token: string
}

export async function authenticate(
{ owner, repo }: Context['repo'],
fallbackToken: string
): Promise<string> {
try {
const response = await fetch(
`${GITHUB_AUTH_SERVICE_URL}/github/${owner}/${repo}/installation-token`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${GITHUB_AUTH_API_KEY}`
}
}
)
const data = await response.json()
if (response.ok && isTokenResponse(data)) {
core.info('Using Code PushUp GitHub App authentication')
return data.token
}
handleErrorResponse(response.status)
} catch (error) {
core.warning(
`Unable to contact Code PushUp authentication service: ${error}`
)
}
core.info('Using standard token authentication')
return fallbackToken
}

function isTokenResponse(res: unknown): res is TokenResponse {
return (
!!res &&
typeof res === 'object' &&
'token' in res &&
typeof res.token === 'string'
)
}

function handleErrorResponse(status: number): void {
switch (status) {
case 404:
core.debug('Code PushUp GitHub App not installed on this repository')
break
case 401:
core.warning('Code PushUp authentication service authorization failed')
break
case 500:
core.warning('Code PushUp authentication service temporarily unavailable')
break
default:
core.debug(`Code PushUp authentication service returned status ${status}`)
}
}
6 changes: 5 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { simpleGit } from 'simple-git'
import { createAnnotationsFromIssues } from './annotations'
import { GitHubApiClient } from './api'
import { REPORT_ARTIFACT_NAME, uploadArtifact } from './artifact'
import { authenticate } from './auth'
import { parseInputs } from './inputs'
import { createOptions } from './options'
import { parseGitRefs } from './refs'
Expand Down Expand Up @@ -44,7 +45,10 @@ export async function run(
}

const refs = parseGitRefs()
const api = new GitHubApiClient(inputs.token, refs, artifact, getOctokit)

const token = await authenticate(github.context.repo, inputs.token)

const api = new GitHubApiClient(token, refs, artifact, getOctokit)

const result = await runInCI(refs, api, options, git)

Expand Down
Loading