Compare commits

...
Author SHA1 Message Date
Thomas Boop dd8d7c3551 exclude dev dependencies while we work on removing lerna 2024-08-22 10:04:12 -04:00
Thomas Boop 59400e2013 Revert "update versions"
139b3391a0
2024-08-22 09:39:02 -04:00
Thomas Boop 139b3391a0 update versions 2024-08-22 09:36:46 -04:00
Thomas Boop 52bce6254b Revert "fix audit"
724956ffa7
2024-08-22 09:33:06 -04:00
Thomas Boop 724956ffa7 fix audit 2024-08-22 09:27:28 -04:00
Thomas Boop 1fc20f076d http-client 2.2.3 2024-08-22 09:09:44 -04:00
Brian DeHamerandGitHub 7298ff3219 Merge pull request #1799 from actions/bdehamer/http-client-proxy-auth
fix encoding for proxy auth token
2024-08-21 06:41:49 -07:00
Brian DeHamerandGitHub 571d782946 Merge pull request #1797 from actions/bdehamer/attester-release-notes
improve release notes for @actions/attest
2024-08-19 07:38:36 -07:00
Brian DeHamer ada9e00cda fix encoding for proxy auth token
Signed-off-by: Brian DeHamer <[email protected]>
2024-08-16 15:03:40 -07:00
Josh GrossandGitHub faf9cb2ea2 Include the package name in the Publish Workflow run (#1793) 2024-08-16 16:15:14 -04:00
Brian DeHamer ac3a063583 improve release notes for @actions/attest
Signed-off-by: Brian DeHamer <[email protected]>
2024-08-16 12:43:39 -07:00
Brian DeHamerandGitHub 7cc96bb976 Merge pull request #1796 from actions/bdehamer/attest-issuer
derive default OIDC issuer from current tenant
2024-08-16 12:21:00 -07:00
Brian DeHamer fa6cc53297 derive default OIDC issuer from current tenant
Signed-off-by: Brian DeHamer <[email protected]>
2024-08-16 12:07:23 -07:00
Thomas BoopandGitHub f299e8ba1e HTTP Client 2.2.2 Release (#1794)
* 2.2.2 release

* update nodes
2024-08-16 13:11:10 -04:00
YuandGitHub 1b9927d1c7 Handle Encoded URL for Proxy Username and Password in HTTP Client (#1782)
* uri-decode-fix

Signed-off-by: Yu <[email protected]>

* http-client URLdecode fix

Signed-off-by: Yu <[email protected]>

* http-client URLdecode test typo fix

Signed-off-by: Yu <[email protected]>

---------

Signed-off-by: Yu <[email protected]>
2024-08-16 12:43:10 -04:00
Brian DeHamerandGitHub 279e891118 Merge pull request #1790 from actions/bdehamer/attest-headers
support for headers param in attest functions
2024-08-16 07:21:46 -07:00
Brian DeHamer 340a1033a5 support for headers param in attest functions
Signed-off-by: Brian DeHamer <[email protected]>
2024-08-15 15:35:32 -07:00
Josh GrossandGitHub 50f2977cce Add glob option to ignore hidden files (#1791)
* Add glob option to ignore hidden files

* Use the basename of the file/directory to check for `.`

* Ensure the `excludeHiddenFiles` is properly copied

* Allow the root directory to be matched

* Fix description of `excludeHiddenFiles`

* Document Windows hidden attribute limitation

* Bump version

* `lint`

* Document 0.5.0 release

* Lint again
2024-08-15 17:13:49 -04:00
Thomas BoopandGitHub 48a65377c0 Fix HTTP client tests (#1792)
* fix tests and update dependencies
2024-08-15 16:53:06 -04:00
Rob HerleyandGitHub f003268b32 Merge pull request #1786 from SMoraisAnsys/fix/chunk-timeout
refactor: set chunk timeout back to 5 minutes
2024-08-06 12:12:38 -04:00
Sébastien MoraisandGitHub 3a33cca851 FIX: Set chunk timeout back to 5 minutes 2024-08-06 10:27:41 +02:00
Rob HerleyandGitHub bb6c500939 Merge pull request #1781 from actions/robherley/artifact-2.1.9
Prep for @actions/artifact v2.1.9
2024-08-01 09:42:30 -04:00
Rob Herley 76b6e24aee bump pkg lock 2024-07-31 10:12:04 -04:00
Rob Herley 58d14c4ef5 prep for @actions/artifact v2.1.9 2024-07-31 10:05:34 -04:00
Rob HerleyandGitHub 7463cf3da6 Merge pull request #1771 from rmunn/fix-too-many-open-files
Prevent "too many open files" in artifact upload
2024-07-31 09:20:36 -04:00
Brian DeHamerandGitHub 90d9783552 Merge pull request #1776 from actions/bdehamer/jwks-proxy-fix
fix proxy support for jwks retrieval
2024-07-29 16:31:41 -07:00
Robin Munn 7c61054649 Remove unused import 2024-07-27 17:00:02 +07:00
Brian DeHamer b28406bd1f fix proxy support for jwks retrieval
Signed-off-by: Brian DeHamer <[email protected]>
2024-07-26 15:03:40 -07:00
Robin Munn 9517cdf52d Prevent "too many open files" in artifact upload
See https://www.archiverjs.com/docs/archiver/#file
2024-07-26 08:49:34 +07:00
Rob HerleyandGitHub 49927e464a Merge pull request #1774 from actions/robherley/fix-chunk-timeout
Fix artifact upload chunk timeout logic + update tests
2024-07-25 13:52:09 -04:00
Rob Herley 3e34f6d19c add comment for chunk timeout 2024-07-24 12:40:57 -04:00
Rob Herley 182702d2df fix chunk timeout + update tests 2024-07-23 21:57:39 -04:00
Rob HerleyandGitHub 1db73622df Merge pull request #1768 from actions/robherley/artifacts-allow-localhost
Allow localhost hostnames for artifact checks
2024-07-03 14:38:52 -04:00
37 changed files with 497 additions and 1009 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
run: npm run bootstrap run: npm run bootstrap
- name: audit tools (without allow-list) - name: audit tools (without allow-list)
run: npm audit --audit-level=moderate run: npm audit --audit-level=moderate --omit dev
- name: audit packages - name: audit packages
run: npm run audit-all run: npm run audit-all
+2
View File
@@ -1,5 +1,7 @@
name: Publish NPM name: Publish NPM
run-name: Publish NPM - ${{ github.event.inputs.package }}
on: on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
+5 -4
View File
@@ -4925,12 +4925,13 @@
} }
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.6.2", "version": "1.7.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.0", "follow-redirects": "^1.15.6",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"proxy-from-env": "^1.1.0" "proxy-from-env": "^1.1.0"
} }
+5
View File
@@ -1,5 +1,10 @@
# @actions/artifact Releases # @actions/artifact Releases
### 2.1.9
- Fixed artifact upload chunk timeout logic [#1774](https://github.com/actions/toolkit/pull/1774)
- Use lazy stream to prevent issues with open file limits [#1771](https://github.com/actions/toolkit/pull/1771)
### 2.1.8 ### 2.1.8
- Allows `*.localhost` domains for hostname checks for local development. - Allows `*.localhost` domains for hostname checks for local development.
@@ -1,260 +1,137 @@
import * as uploadZipSpecification from '../src/internal/upload/upload-zip-specification' import * as uploadZipSpecification from '../src/internal/upload/upload-zip-specification'
import * as zip from '../src/internal/upload/zip' import * as zip from '../src/internal/upload/zip'
import * as util from '../src/internal/shared/util' import * as util from '../src/internal/shared/util'
import * as retention from '../src/internal/upload/retention'
import * as config from '../src/internal/shared/config' import * as config from '../src/internal/shared/config'
import {Timestamp, ArtifactServiceClientJSON} from '../src/generated' import {ArtifactServiceClientJSON} from '../src/generated'
import * as blobUpload from '../src/internal/upload/blob-upload' import * as blobUpload from '../src/internal/upload/blob-upload'
import {uploadArtifact} from '../src/internal/upload/upload-artifact' import {uploadArtifact} from '../src/internal/upload/upload-artifact'
import {noopLogs} from './common' import {noopLogs} from './common'
import {FilesNotFoundError} from '../src/internal/shared/errors' import {FilesNotFoundError} from '../src/internal/shared/errors'
import {BlockBlobClient} from '@azure/storage-blob' import {BlockBlobUploadStreamOptions} from '@azure/storage-blob'
import * as fs from 'fs' import * as fs from 'fs'
import * as path from 'path' import * as path from 'path'
const uploadStreamMock = jest.fn()
const blockBlobClientMock = jest.fn().mockImplementation(() => ({
uploadStream: uploadStreamMock
}))
jest.mock('@azure/storage-blob', () => ({
BlobClient: jest.fn().mockImplementation(() => {
return {
getBlockBlobClient: blockBlobClientMock
}
})
}))
const fixtures = {
uploadDirectory: path.join(__dirname, '_temp', 'plz-upload'),
files: [
['file1.txt', 'test 1 file content'],
['file2.txt', 'test 2 file content'],
['file3.txt', 'test 3 file content']
],
backendIDs: {
workflowRunBackendId: '67dbcc20-e851-4452-a7c3-2cc0d2e0ec67',
workflowJobRunBackendId: '5f49179d-3386-4c38-85f7-00f8138facd0'
},
runtimeToken: 'test-token',
resultsServiceURL: 'http://results.local',
inputs: {
artifactName: 'test-artifact',
files: [
'/home/user/files/plz-upload/file1.txt',
'/home/user/files/plz-upload/file2.txt',
'/home/user/files/plz-upload/dir/file3.txt'
],
rootDirectory: '/home/user/files/plz-upload'
}
}
describe('upload-artifact', () => { describe('upload-artifact', () => {
beforeAll(() => {
if (!fs.existsSync(fixtures.uploadDirectory)) {
fs.mkdirSync(fixtures.uploadDirectory, {recursive: true})
}
for (const [file, content] of fixtures.files) {
fs.writeFileSync(path.join(fixtures.uploadDirectory, file), content)
}
})
beforeEach(() => { beforeEach(() => {
noopLogs() noopLogs()
jest
.spyOn(uploadZipSpecification, 'validateRootDirectory')
.mockReturnValue()
jest
.spyOn(util, 'getBackendIdsFromToken')
.mockReturnValue(fixtures.backendIDs)
jest
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
.mockReturnValue(
fixtures.files.map(file => ({
sourcePath: path.join(fixtures.uploadDirectory, file[0]),
destinationPath: file[0]
}))
)
jest.spyOn(config, 'getRuntimeToken').mockReturnValue(fixtures.runtimeToken)
jest
.spyOn(config, 'getResultsServiceUrl')
.mockReturnValue(fixtures.resultsServiceURL)
}) })
afterEach(() => { afterEach(() => {
jest.restoreAllMocks() jest.restoreAllMocks()
}) })
it('should successfully upload an artifact', () => { it('should reject if there are no files to upload', async () => {
const mockDate = new Date('2020-01-01')
jest
.spyOn(uploadZipSpecification, 'validateRootDirectory')
.mockReturnValue()
jest
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
.mockReturnValue([
{
sourcePath: '/home/user/files/plz-upload/file1.txt',
destinationPath: 'file1.txt'
},
{
sourcePath: '/home/user/files/plz-upload/file2.txt',
destinationPath: 'file2.txt'
},
{
sourcePath: '/home/user/files/plz-upload/dir/file3.txt',
destinationPath: 'dir/file3.txt'
}
])
jest
.spyOn(zip, 'createZipUploadStream')
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
jest.spyOn(util, 'getBackendIdsFromToken').mockReturnValue({
workflowRunBackendId: '1234',
workflowJobRunBackendId: '5678'
})
jest
.spyOn(retention, 'getExpiration')
.mockReturnValue(Timestamp.fromDate(mockDate))
jest
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
.mockReturnValue(
Promise.resolve({
ok: true,
signedUploadUrl: 'https://signed-upload-url.com'
})
)
jest.spyOn(blobUpload, 'uploadZipToBlobStorage').mockReturnValue(
Promise.resolve({
uploadSize: 1234,
sha256Hash: 'test-sha256-hash'
})
)
jest
.spyOn(ArtifactServiceClientJSON.prototype, 'FinalizeArtifact')
.mockReturnValue(Promise.resolve({ok: true, artifactId: '1'}))
// ArtifactHttpClient mocks
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
jest
.spyOn(config, 'getResultsServiceUrl')
.mockReturnValue('https://test-url.com')
const uploadResp = uploadArtifact(
'test-artifact',
[
'/home/user/files/plz-upload/file1.txt',
'/home/user/files/plz-upload/file2.txt',
'/home/user/files/plz-upload/dir/file3.txt'
],
'/home/user/files/plz-upload'
)
expect(uploadResp).resolves.toEqual({size: 1234, id: 1})
})
it('should throw an error if the root directory is invalid', () => {
jest
.spyOn(uploadZipSpecification, 'validateRootDirectory')
.mockImplementation(() => {
throw new Error('Invalid root directory')
})
const uploadResp = uploadArtifact(
'test-artifact',
[
'/home/user/files/plz-upload/file1.txt',
'/home/user/files/plz-upload/file2.txt',
'/home/user/files/plz-upload/dir/file3.txt'
],
'/home/user/files/plz-upload'
)
expect(uploadResp).rejects.toThrow('Invalid root directory')
})
it('should reject if there are no files to upload', () => {
jest
.spyOn(uploadZipSpecification, 'validateRootDirectory')
.mockReturnValue()
jest jest
.spyOn(uploadZipSpecification, 'getUploadZipSpecification') .spyOn(uploadZipSpecification, 'getUploadZipSpecification')
.mockClear()
.mockReturnValue([]) .mockReturnValue([])
const uploadResp = uploadArtifact( const uploadResp = uploadArtifact(
'test-artifact', fixtures.inputs.artifactName,
[ fixtures.inputs.files,
'/home/user/files/plz-upload/file1.txt', fixtures.inputs.rootDirectory
'/home/user/files/plz-upload/file2.txt',
'/home/user/files/plz-upload/dir/file3.txt'
],
'/home/user/files/plz-upload'
) )
expect(uploadResp).rejects.toThrowError(FilesNotFoundError) await expect(uploadResp).rejects.toThrowError(FilesNotFoundError)
}) })
it('should reject if no backend IDs are found', () => { it('should reject if no backend IDs are found', async () => {
jest jest.spyOn(util, 'getBackendIdsFromToken').mockRestore()
.spyOn(uploadZipSpecification, 'validateRootDirectory')
.mockReturnValue()
jest
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
.mockReturnValue([
{
sourcePath: '/home/user/files/plz-upload/file1.txt',
destinationPath: 'file1.txt'
},
{
sourcePath: '/home/user/files/plz-upload/file2.txt',
destinationPath: 'file2.txt'
},
{
sourcePath: '/home/user/files/plz-upload/dir/file3.txt',
destinationPath: 'dir/file3.txt'
}
])
jest
.spyOn(zip, 'createZipUploadStream')
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
const uploadResp = uploadArtifact( const uploadResp = uploadArtifact(
'test-artifact', fixtures.inputs.artifactName,
[ fixtures.inputs.files,
'/home/user/files/plz-upload/file1.txt', fixtures.inputs.rootDirectory
'/home/user/files/plz-upload/file2.txt',
'/home/user/files/plz-upload/dir/file3.txt'
],
'/home/user/files/plz-upload'
) )
expect(uploadResp).rejects.toThrow() await expect(uploadResp).rejects.toThrow()
}) })
it('should return false if the creation request fails', () => { it('should return false if the creation request fails', async () => {
const mockDate = new Date('2020-01-01')
jest
.spyOn(uploadZipSpecification, 'validateRootDirectory')
.mockReturnValue()
jest
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
.mockReturnValue([
{
sourcePath: '/home/user/files/plz-upload/file1.txt',
destinationPath: 'file1.txt'
},
{
sourcePath: '/home/user/files/plz-upload/file2.txt',
destinationPath: 'file2.txt'
},
{
sourcePath: '/home/user/files/plz-upload/dir/file3.txt',
destinationPath: 'dir/file3.txt'
}
])
jest jest
.spyOn(zip, 'createZipUploadStream') .spyOn(zip, 'createZipUploadStream')
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1))) .mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
jest.spyOn(util, 'getBackendIdsFromToken').mockReturnValue({
workflowRunBackendId: '1234',
workflowJobRunBackendId: '5678'
})
jest
.spyOn(retention, 'getExpiration')
.mockReturnValue(Timestamp.fromDate(mockDate))
jest jest
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact') .spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
.mockReturnValue(Promise.resolve({ok: false, signedUploadUrl: ''})) .mockReturnValue(Promise.resolve({ok: false, signedUploadUrl: ''}))
// ArtifactHttpClient mocks
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
jest
.spyOn(config, 'getResultsServiceUrl')
.mockReturnValue('https://test-url.com')
const uploadResp = uploadArtifact( const uploadResp = uploadArtifact(
'test-artifact', fixtures.inputs.artifactName,
[ fixtures.inputs.files,
'/home/user/files/plz-upload/file1.txt', fixtures.inputs.rootDirectory
'/home/user/files/plz-upload/file2.txt',
'/home/user/files/plz-upload/dir/file3.txt'
],
'/home/user/files/plz-upload'
) )
expect(uploadResp).rejects.toThrow() await expect(uploadResp).rejects.toThrow()
}) })
it('should return false if blob storage upload is unsuccessful', () => { it('should return false if blob storage upload is unsuccessful', async () => {
const mockDate = new Date('2020-01-01')
jest
.spyOn(uploadZipSpecification, 'validateRootDirectory')
.mockReturnValue()
jest
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
.mockReturnValue([
{
sourcePath: '/home/user/files/plz-upload/file1.txt',
destinationPath: 'file1.txt'
},
{
sourcePath: '/home/user/files/plz-upload/file2.txt',
destinationPath: 'file2.txt'
},
{
sourcePath: '/home/user/files/plz-upload/dir/file3.txt',
destinationPath: 'dir/file3.txt'
}
])
jest jest
.spyOn(zip, 'createZipUploadStream') .spyOn(zip, 'createZipUploadStream')
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1))) .mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
jest.spyOn(util, 'getBackendIdsFromToken').mockReturnValue({
workflowRunBackendId: '1234',
workflowJobRunBackendId: '5678'
})
jest
.spyOn(retention, 'getExpiration')
.mockReturnValue(Timestamp.fromDate(mockDate))
jest jest
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact') .spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
.mockReturnValue( .mockReturnValue(
@@ -267,57 +144,19 @@ describe('upload-artifact', () => {
.spyOn(blobUpload, 'uploadZipToBlobStorage') .spyOn(blobUpload, 'uploadZipToBlobStorage')
.mockReturnValue(Promise.reject(new Error('boom'))) .mockReturnValue(Promise.reject(new Error('boom')))
// ArtifactHttpClient mocks
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
jest
.spyOn(config, 'getResultsServiceUrl')
.mockReturnValue('https://test-url.com')
const uploadResp = uploadArtifact( const uploadResp = uploadArtifact(
'test-artifact', fixtures.inputs.artifactName,
[ fixtures.inputs.files,
'/home/user/files/plz-upload/file1.txt', fixtures.inputs.rootDirectory
'/home/user/files/plz-upload/file2.txt',
'/home/user/files/plz-upload/dir/file3.txt'
],
'/home/user/files/plz-upload'
) )
expect(uploadResp).rejects.toThrow() await expect(uploadResp).rejects.toThrow()
}) })
it('should reject if finalize artifact fails', () => { it('should reject if finalize artifact fails', async () => {
const mockDate = new Date('2020-01-01')
jest
.spyOn(uploadZipSpecification, 'validateRootDirectory')
.mockReturnValue()
jest
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
.mockReturnValue([
{
sourcePath: '/home/user/files/plz-upload/file1.txt',
destinationPath: 'file1.txt'
},
{
sourcePath: '/home/user/files/plz-upload/file2.txt',
destinationPath: 'file2.txt'
},
{
sourcePath: '/home/user/files/plz-upload/dir/file3.txt',
destinationPath: 'dir/file3.txt'
}
])
jest jest
.spyOn(zip, 'createZipUploadStream') .spyOn(zip, 'createZipUploadStream')
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1))) .mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
jest.spyOn(util, 'getBackendIdsFromToken').mockReturnValue({
workflowRunBackendId: '1234',
workflowJobRunBackendId: '5678'
})
jest
.spyOn(retention, 'getExpiration')
.mockReturnValue(Timestamp.fromDate(mockDate))
jest jest
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact') .spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
.mockReturnValue( .mockReturnValue(
@@ -336,112 +175,113 @@ describe('upload-artifact', () => {
.spyOn(ArtifactServiceClientJSON.prototype, 'FinalizeArtifact') .spyOn(ArtifactServiceClientJSON.prototype, 'FinalizeArtifact')
.mockReturnValue(Promise.resolve({ok: false, artifactId: ''})) .mockReturnValue(Promise.resolve({ok: false, artifactId: ''}))
// ArtifactHttpClient mocks
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
jest
.spyOn(config, 'getResultsServiceUrl')
.mockReturnValue('https://test-url.com')
const uploadResp = uploadArtifact( const uploadResp = uploadArtifact(
'test-artifact', fixtures.inputs.artifactName,
[ fixtures.inputs.files,
'/home/user/files/plz-upload/file1.txt', fixtures.inputs.rootDirectory
'/home/user/files/plz-upload/file2.txt',
'/home/user/files/plz-upload/dir/file3.txt'
],
'/home/user/files/plz-upload'
) )
expect(uploadResp).rejects.toThrow() await expect(uploadResp).rejects.toThrow()
}) })
it('should throw an error uploading blob chunks get delayed', async () => { it('should successfully upload an artifact', async () => {
const mockDate = new Date('2020-01-01')
const dirPath = path.join(__dirname, `plz-upload`)
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, {recursive: true})
}
fs.writeFileSync(path.join(dirPath, 'file1.txt'), 'test file content')
fs.writeFileSync(path.join(dirPath, 'file2.txt'), 'test file content')
fs.writeFileSync(path.join(dirPath, 'file3.txt'), 'test file content')
jest
.spyOn(uploadZipSpecification, 'validateRootDirectory')
.mockReturnValue()
jest
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
.mockReturnValue([
{
sourcePath: path.join(dirPath, 'file1.txt'),
destinationPath: 'file1.txt'
},
{
sourcePath: path.join(dirPath, 'file2.txt'),
destinationPath: 'file2.txt'
},
{
sourcePath: path.join(dirPath, 'file3.txt'),
destinationPath: 'dir/file3.txt'
}
])
jest.spyOn(util, 'getBackendIdsFromToken').mockReturnValue({
workflowRunBackendId: '1234',
workflowJobRunBackendId: '5678'
})
jest
.spyOn(retention, 'getExpiration')
.mockReturnValue(Timestamp.fromDate(mockDate))
jest jest
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact') .spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
.mockReturnValue( .mockReturnValue(
Promise.resolve({ Promise.resolve({
ok: true, ok: true,
signedUploadUrl: 'https://signed-upload-url.com' signedUploadUrl: 'https://signed-upload-url.local'
}) })
) )
jest jest
.spyOn(blobUpload, 'uploadZipToBlobStorage') .spyOn(ArtifactServiceClientJSON.prototype, 'FinalizeArtifact')
.mockReturnValue(Promise.reject(new Error('Upload progress stalled.'))) .mockReturnValue(
Promise.resolve({
// ArtifactHttpClient mocks ok: true,
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token') artifactId: '1'
jest })
.spyOn(config, 'getResultsServiceUrl')
.mockReturnValue('https://test-url.com')
BlockBlobClient.prototype.uploadStream = jest
.fn()
.mockImplementation(
async (stream, bufferSize, maxConcurrency, options) => {
return new Promise<void>(resolve => {
// Call the onProgress callback with a progress event
options.onProgress({loadedBytes: 0})
// Wait for 31 seconds before resolving the promise
setTimeout(() => {
// Call the onProgress callback again to simulate progress
options.onProgress({loadedBytes: 100})
resolve()
}, 31000) // Delay longer than your timeout
})
}
) )
jest.mock('fs') uploadStreamMock.mockImplementation(
const uploadResp = uploadArtifact( async (
'test-artifact', stream: NodeJS.ReadableStream,
[ bufferSize?: number,
'/home/user/files/plz-upload/file1.txt', maxConcurrency?: number,
'/home/user/files/plz-upload/file2.txt', options?: BlockBlobUploadStreamOptions
'/home/user/files/plz-upload/dir/file3.txt' ) => {
], const {onProgress, abortSignal} = options || {}
'/home/user/files/plz-upload'
onProgress?.({loadedBytes: 0})
return new Promise(resolve => {
const timerId = setTimeout(() => {
onProgress?.({loadedBytes: 256})
resolve({})
}, 1_000)
abortSignal?.addEventListener('abort', () => {
clearTimeout(timerId)
resolve({})
})
})
}
) )
expect(uploadResp).rejects.toThrow('Upload progress stalled.') const {id, size} = await uploadArtifact(
fixtures.inputs.artifactName,
fixtures.inputs.files,
fixtures.inputs.rootDirectory
)
expect(id).toBe(1)
expect(size).toBe(256)
})
it('should throw an error uploading blob chunks get delayed', async () => {
jest
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
.mockReturnValue(
Promise.resolve({
ok: true,
signedUploadUrl: 'https://signed-upload-url.local'
})
)
jest
.spyOn(ArtifactServiceClientJSON.prototype, 'FinalizeArtifact')
.mockReturnValue(
Promise.resolve({
ok: true,
artifactId: '1'
})
)
jest
.spyOn(config, 'getResultsServiceUrl')
.mockReturnValue('https://results.local')
jest.spyOn(config, 'getUploadChunkTimeout').mockReturnValue(2_000)
uploadStreamMock.mockImplementation(
async (
stream: NodeJS.ReadableStream,
bufferSize?: number,
maxConcurrency?: number,
options?: BlockBlobUploadStreamOptions
) => {
const {onProgress, abortSignal} = options || {}
onProgress?.({loadedBytes: 0})
return new Promise(resolve => {
abortSignal?.addEventListener('abort', () => {
resolve({})
})
})
}
)
const uploadResp = uploadArtifact(
fixtures.inputs.artifactName,
fixtures.inputs.files,
fixtures.inputs.rootDirectory
)
await expect(uploadResp).rejects.toThrow('Upload progress stalled.')
}) })
}) })
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "@actions/artifact", "name": "@actions/artifact",
"version": "2.1.8", "version": "2.1.9",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@actions/artifact", "name": "@actions/artifact",
"version": "2.1.8", "version": "2.1.9",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.10.0", "@actions/core": "^1.10.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@actions/artifact", "name": "@actions/artifact",
"version": "2.1.8", "version": "2.1.9",
"preview": true, "preview": true,
"description": "Actions artifact lib", "description": "Actions artifact lib",
"keywords": [ "keywords": [
@@ -57,3 +57,7 @@ export function getConcurrency(): number {
const concurrency = 16 * numCPUs const concurrency = 16 * numCPUs
return concurrency > 300 ? 300 : concurrency return concurrency > 300 ? 300 : concurrency
} }
export function getUploadChunkTimeout(): number {
return 300_000 // 5 minutes
}
@@ -1,7 +1,11 @@
import {BlobClient, BlockBlobUploadStreamOptions} from '@azure/storage-blob' import {BlobClient, BlockBlobUploadStreamOptions} from '@azure/storage-blob'
import {TransferProgressEvent} from '@azure/core-http' import {TransferProgressEvent} from '@azure/core-http'
import {ZipUploadStream} from './zip' import {ZipUploadStream} from './zip'
import {getUploadChunkSize, getConcurrency} from '../shared/config' import {
getUploadChunkSize,
getConcurrency,
getUploadChunkTimeout
} from '../shared/config'
import * as core from '@actions/core' import * as core from '@actions/core'
import * as crypto from 'crypto' import * as crypto from 'crypto'
import * as stream from 'stream' import * as stream from 'stream'
@@ -25,29 +29,26 @@ export async function uploadZipToBlobStorage(
): Promise<BlobUploadResponse> { ): Promise<BlobUploadResponse> {
let uploadByteCount = 0 let uploadByteCount = 0
let lastProgressTime = Date.now() let lastProgressTime = Date.now()
let timeoutId: NodeJS.Timeout | undefined const abortController = new AbortController()
const chunkTimer = (timeout: number): NodeJS.Timeout => { const chunkTimer = async (interval: number): Promise<void> =>
// clear the previous timeout new Promise((resolve, reject) => {
if (timeoutId) { const timer = setInterval(() => {
clearTimeout(timeoutId) if (Date.now() - lastProgressTime > interval) {
} reject(new Error('Upload progress stalled.'))
}
}, interval)
abortController.signal.addEventListener('abort', () => {
clearInterval(timer)
resolve()
})
})
timeoutId = setTimeout(() => {
const now = Date.now()
// if there's been more than 30 seconds since the
// last progress event, then we'll consider the upload stalled
if (now - lastProgressTime > timeout) {
throw new Error('Upload progress stalled.')
}
}, timeout)
return timeoutId
}
const maxConcurrency = getConcurrency() const maxConcurrency = getConcurrency()
const bufferSize = getUploadChunkSize() const bufferSize = getUploadChunkSize()
const blobClient = new BlobClient(authenticatedUploadURL) const blobClient = new BlobClient(authenticatedUploadURL)
const blockBlobClient = blobClient.getBlockBlobClient() const blockBlobClient = blobClient.getBlockBlobClient()
const timeoutDuration = 300000 // 30 seconds
core.debug( core.debug(
`Uploading artifact zip to blob storage with maxConcurrency: ${maxConcurrency}, bufferSize: ${bufferSize}` `Uploading artifact zip to blob storage with maxConcurrency: ${maxConcurrency}, bufferSize: ${bufferSize}`
@@ -56,13 +57,13 @@ export async function uploadZipToBlobStorage(
const uploadCallback = (progress: TransferProgressEvent): void => { const uploadCallback = (progress: TransferProgressEvent): void => {
core.info(`Uploaded bytes ${progress.loadedBytes}`) core.info(`Uploaded bytes ${progress.loadedBytes}`)
uploadByteCount = progress.loadedBytes uploadByteCount = progress.loadedBytes
chunkTimer(timeoutDuration)
lastProgressTime = Date.now() lastProgressTime = Date.now()
} }
const options: BlockBlobUploadStreamOptions = { const options: BlockBlobUploadStreamOptions = {
blobHTTPHeaders: {blobContentType: 'zip'}, blobHTTPHeaders: {blobContentType: 'zip'},
onProgress: uploadCallback onProgress: uploadCallback,
abortSignal: abortController.signal
} }
let sha256Hash: string | undefined = undefined let sha256Hash: string | undefined = undefined
@@ -75,24 +76,22 @@ export async function uploadZipToBlobStorage(
core.info('Beginning upload of artifact content to blob storage') core.info('Beginning upload of artifact content to blob storage')
try { try {
// Start the chunk timer await Promise.race([
timeoutId = chunkTimer(timeoutDuration) blockBlobClient.uploadStream(
await blockBlobClient.uploadStream( uploadStream,
uploadStream, bufferSize,
bufferSize, maxConcurrency,
maxConcurrency, options
options ),
) chunkTimer(getUploadChunkTimeout())
])
} catch (error) { } catch (error) {
if (NetworkError.isNetworkErrorCode(error?.code)) { if (NetworkError.isNetworkErrorCode(error?.code)) {
throw new NetworkError(error?.code) throw new NetworkError(error?.code)
} }
throw error throw error
} finally { } finally {
// clear the timeout whether or not the upload completes abortController.abort()
if (timeoutId) {
clearTimeout(timeoutId)
}
} }
core.info('Finished uploading artifact content to blob storage!') core.info('Finished uploading artifact content to blob storage!')
+1 -2
View File
@@ -1,7 +1,6 @@
import * as stream from 'stream' import * as stream from 'stream'
import * as archiver from 'archiver' import * as archiver from 'archiver'
import * as core from '@actions/core' import * as core from '@actions/core'
import {createReadStream} from 'fs'
import {UploadZipSpecification} from './upload-zip-specification' import {UploadZipSpecification} from './upload-zip-specification'
import {getUploadChunkSize} from '../shared/config' import {getUploadChunkSize} from '../shared/config'
@@ -44,7 +43,7 @@ export async function createZipUploadStream(
for (const file of uploadSpecification) { for (const file of uploadSpecification) {
if (file.sourcePath !== null) { if (file.sourcePath !== null) {
// Add a normal file to the zip // Add a normal file to the zip
zip.append(createReadStream(file.sourcePath), { zip.file(file.sourcePath, {
name: file.destinationPath name: file.destinationPath
}) })
} else { } else {
+4
View File
@@ -63,6 +63,8 @@ export type AttestOptions = {
// Sigstore instance to use for signing. Must be one of "public-good" or // Sigstore instance to use for signing. Must be one of "public-good" or
// "github". // "github".
sigstore?: 'public-good' | 'github' sigstore?: 'public-good' | 'github'
// HTTP headers to include in request to attestations API.
headers?: {[header: string]: string | number | undefined}
// Whether to skip writing the attestation to the GH attestations API. // Whether to skip writing the attestation to the GH attestations API.
skipWrite?: boolean skipWrite?: boolean
} }
@@ -113,6 +115,8 @@ export type AttestProvenanceOptions = {
// Sigstore instance to use for signing. Must be one of "public-good" or // Sigstore instance to use for signing. Must be one of "public-good" or
// "github". // "github".
sigstore?: 'public-good' | 'github' sigstore?: 'public-good' | 'github'
// HTTP headers to include in request to attestations API.
headers?: {[header: string]: string | number | undefined}
// Whether to skip writing the attestation to the GH attestations API. // Whether to skip writing the attestation to the GH attestations API.
skipWrite?: boolean skipWrite?: boolean
// Issuer URL responsible for minting the OIDC token from which the // Issuer URL responsible for minting the OIDC token from which the
+20 -11
View File
@@ -1,27 +1,36 @@
# @actions/attest Releases # @actions/attest Releases
### 1.4.0
- Add new `headers` parameter to the `attest` and `attestProvenance` functions [#1790](https://github.com/actions/toolkit/pull/1790)
- Update `buildSLSAProvenancePredicate`/`attestProvenance` to automatically derive default OIDC issuer URL from current execution context [#1796](https://github.com/actions/toolkit/pull/1796)
### 1.3.1
- Fix bug with proxy support when retrieving JWKS for OIDC issuer [#1776](https://github.com/actions/toolkit/pull/1776)
### 1.3.0 ### 1.3.0
- Dynamic construction of Sigstore API URLs - Dynamic construction of Sigstore API URLs [#1735](https://github.com/actions/toolkit/pull/1735)
- Switch to new GH provenance build type - Switch to new GH provenance build type [#1745](https://github.com/actions/toolkit/pull/1745)
- Fetch existing Rekor entry on 409 conflict error - Fetch existing Rekor entry on 409 conflict error [#1759](https://github.com/actions/toolkit/pull/1759)
- Bump @sigstore/bundle from 2.3.0 to 2.3.2 - Bump @sigstore/bundle from 2.3.0 to 2.3.2 [#1738](https://github.com/actions/toolkit/pull/1738)
- Bump @sigstore/sign from 2.3.0 to 2.3.2 - Bump @sigstore/sign from 2.3.0 to 2.3.2 [#1738](https://github.com/actions/toolkit/pull/1738)
### 1.2.1 ### 1.2.1
- Retry request on attestation persistence failure - Retry request on attestation persistence failure [#1725](https://github.com/actions/toolkit/pull/1725)
### 1.2.0 ### 1.2.0
- Generate attestations using the v0.3 Sigstore bundle format. - Generate attestations using the v0.3 Sigstore bundle format [#1701](https://github.com/actions/toolkit/pull/1701)
- Bump @sigstore/bundle from 2.2.0 to 2.3.0. - Bump @sigstore/bundle from 2.2.0 to 2.3.0 [#1701](https://github.com/actions/toolkit/pull/1701)
- Bump @sigstore/sign from 2.2.3 to 2.3.0. - Bump @sigstore/sign from 2.2.3 to 2.3.0 [#1701](https://github.com/actions/toolkit/pull/1701)
- Remove dependency on make-fetch-happen - Remove dependency on make-fetch-happen [#1714](https://github.com/actions/toolkit/pull/1714)
### 1.1.0 ### 1.1.0
- Updates the `attestProvenance` function to retrieve a token from the GitHub OIDC provider and use the token claims to populate the provenance statement. - Updates the `attestProvenance` function to retrieve a token from the GitHub OIDC provider and use the token claims to populate the provenance statement [#1693](https://github.com/actions/toolkit/pull/1693)
### 1.0.0 ### 1.0.0
@@ -9,7 +9,7 @@ exports[`provenance functions buildSLSAProvenancePredicate returns a provenance
"workflow": { "workflow": {
"path": ".github/workflows/main.yml", "path": ".github/workflows/main.yml",
"ref": "main", "ref": "main",
"repository": "https://github.com/owner/repo", "repository": "https://foo.ghe.com/owner/repo",
}, },
}, },
"internalParameters": { "internalParameters": {
@@ -25,16 +25,16 @@ exports[`provenance functions buildSLSAProvenancePredicate returns a provenance
"digest": { "digest": {
"gitCommit": "babca52ab0c93ae16539e5923cb0d7403b9a093b", "gitCommit": "babca52ab0c93ae16539e5923cb0d7403b9a093b",
}, },
"uri": "git+https://github.com/owner/repo@refs/heads/main", "uri": "git+https://foo.ghe.com/owner/repo@refs/heads/main",
}, },
], ],
}, },
"runDetails": { "runDetails": {
"builder": { "builder": {
"id": "https://github.com/owner/workflows/.github/workflows/publish.yml@main", "id": "https://foo.ghe.com/owner/workflows/.github/workflows/publish.yml@main",
}, },
"metadata": { "metadata": {
"invocationId": "https://github.com/owner/repo/actions/runs/run-id/attempts/run-attempt", "invocationId": "https://foo.ghe.com/owner/repo/actions/runs/run-id/attempts/run-attempt",
}, },
}, },
}, },
+2 -2
View File
@@ -99,7 +99,7 @@ describe('getIDTokenClaims', () => {
}) })
it('throws an error', async () => { it('throws an error', async () => {
await expect(getIDTokenClaims(issuer)).rejects.toThrow(/issuer invalid/) await expect(getIDTokenClaims(issuer)).rejects.toThrow(/unexpected "iss"/)
}) })
}) })
@@ -115,7 +115,7 @@ describe('getIDTokenClaims', () => {
}) })
it('throw an error', async () => { it('throw an error', async () => {
await expect(getIDTokenClaims(issuer)).rejects.toThrow(/audience invalid/) await expect(getIDTokenClaims(issuer)).rejects.toThrow(/unexpected "aud"/)
}) })
}) })
+10 -15
View File
@@ -8,7 +8,7 @@ import {attestProvenance, buildSLSAProvenancePredicate} from '../src/provenance'
describe('provenance functions', () => { describe('provenance functions', () => {
const originalEnv = process.env const originalEnv = process.env
const issuer = 'https://example.com' const issuer = 'https://token.actions.foo.ghe.com'
const audience = 'nobody' const audience = 'nobody'
const jwksPath = '/.well-known/jwks.json' const jwksPath = '/.well-known/jwks.json'
const tokenPath = '/token' const tokenPath = '/token'
@@ -38,7 +38,7 @@ describe('provenance functions', () => {
...originalEnv, ...originalEnv,
ACTIONS_ID_TOKEN_REQUEST_URL: `${issuer}${tokenPath}?`, ACTIONS_ID_TOKEN_REQUEST_URL: `${issuer}${tokenPath}?`,
ACTIONS_ID_TOKEN_REQUEST_TOKEN: 'token', ACTIONS_ID_TOKEN_REQUEST_TOKEN: 'token',
GITHUB_SERVER_URL: 'https://github.com', GITHUB_SERVER_URL: 'https://foo.ghe.com',
GITHUB_REPOSITORY: claims.repository GITHUB_REPOSITORY: claims.repository
} }
@@ -68,7 +68,7 @@ describe('provenance functions', () => {
describe('buildSLSAProvenancePredicate', () => { describe('buildSLSAProvenancePredicate', () => {
it('returns a provenance hydrated from an OIDC token', async () => { it('returns a provenance hydrated from an OIDC token', async () => {
const predicate = await buildSLSAProvenancePredicate(issuer) const predicate = await buildSLSAProvenancePredicate()
expect(predicate).toMatchSnapshot() expect(predicate).toMatchSnapshot()
}) })
}) })
@@ -96,9 +96,9 @@ describe('provenance functions', () => {
}) })
describe('when using the github Sigstore instance', () => { describe('when using the github Sigstore instance', () => {
const {fulcioURL, tsaServerURL} = signingEndpoints('github')
beforeEach(async () => { beforeEach(async () => {
const {fulcioURL, tsaServerURL} = signingEndpoints('github')
// Mock Sigstore // Mock Sigstore
await mockFulcio({baseURL: fulcioURL, strict: false}) await mockFulcio({baseURL: fulcioURL, strict: false})
await mockTSA({baseURL: tsaServerURL}) await mockTSA({baseURL: tsaServerURL})
@@ -118,8 +118,7 @@ describe('provenance functions', () => {
subjectName, subjectName,
subjectDigest, subjectDigest,
token: 'token', token: 'token',
sigstore: 'github', sigstore: 'github'
issuer
}) })
expect(attestation).toBeDefined() expect(attestation).toBeDefined()
@@ -146,8 +145,7 @@ describe('provenance functions', () => {
const attestation = await attestProvenance({ const attestation = await attestProvenance({
subjectName, subjectName,
subjectDigest, subjectDigest,
token: 'token', token: 'token'
issuer
}) })
expect(attestation).toBeDefined() expect(attestation).toBeDefined()
@@ -183,8 +181,7 @@ describe('provenance functions', () => {
subjectName, subjectName,
subjectDigest, subjectDigest,
token: 'token', token: 'token',
sigstore: 'public-good', sigstore: 'public-good'
issuer
}) })
expect(attestation).toBeDefined() expect(attestation).toBeDefined()
@@ -211,8 +208,7 @@ describe('provenance functions', () => {
const attestation = await attestProvenance({ const attestation = await attestProvenance({
subjectName, subjectName,
subjectDigest, subjectDigest,
token: 'token', token: 'token'
issuer
}) })
expect(attestation).toBeDefined() expect(attestation).toBeDefined()
@@ -238,8 +234,7 @@ describe('provenance functions', () => {
subjectDigest, subjectDigest,
token: 'token', token: 'token',
sigstore: 'public-good', sigstore: 'public-good',
skipWrite: true, skipWrite: true
issuer
}) })
expect(attestation).toBeDefined() expect(attestation).toBeDefined()
+5 -2
View File
@@ -5,6 +5,7 @@ describe('writeAttestation', () => {
const originalEnv = process.env const originalEnv = process.env
const attestation = {foo: 'bar '} const attestation = {foo: 'bar '}
const token = 'token' const token = 'token'
const headers = {'X-GitHub-Foo': 'true'}
const mockAgent = new MockAgent() const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent) setGlobalDispatcher(mockAgent)
@@ -27,14 +28,16 @@ describe('writeAttestation', () => {
.intercept({ .intercept({
path: '/repos/foo/bar/attestations', path: '/repos/foo/bar/attestations',
method: 'POST', method: 'POST',
headers: {authorization: `token ${token}`}, headers: {authorization: `token ${token}`, ...headers},
body: JSON.stringify({bundle: attestation}) body: JSON.stringify({bundle: attestation})
}) })
.reply(201, {id: '123'}) .reply(201, {id: '123'})
}) })
it('persists the attestation', async () => { it('persists the attestation', async () => {
await expect(writeAttestation(attestation, token)).resolves.toEqual('123') await expect(
writeAttestation(attestation, token, {headers})
).resolves.toEqual('123')
}) })
}) })
+12 -484
View File
@@ -1,12 +1,12 @@
{ {
"name": "@actions/attest", "name": "@actions/attest",
"version": "1.3.0", "version": "1.4.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@actions/attest", "name": "@actions/attest",
"version": "1.3.0", "version": "1.4.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.10.1", "@actions/core": "^1.10.1",
@@ -15,14 +15,12 @@
"@octokit/plugin-retry": "^6.0.1", "@octokit/plugin-retry": "^6.0.1",
"@sigstore/bundle": "^2.3.2", "@sigstore/bundle": "^2.3.2",
"@sigstore/sign": "^2.3.2", "@sigstore/sign": "^2.3.2",
"jsonwebtoken": "^9.0.2", "jose": "^5.2.3"
"jwks-rsa": "^3.1.0"
}, },
"devDependencies": { "devDependencies": {
"@sigstore/mock": "^0.7.4", "@sigstore/mock": "^0.7.4",
"@sigstore/rekor-types": "^2.0.0", "@sigstore/rekor-types": "^2.0.0",
"@types/jsonwebtoken": "^9.0.6", "@types/jsonwebtoken": "^9.0.6",
"jose": "^5.2.3",
"nock": "^13.5.1", "nock": "^13.5.1",
"undici": "^5.28.4" "undici": "^5.28.4"
} }
@@ -561,100 +559,24 @@
"node": "^16.14.0 || >=18.0.0" "node": "^16.14.0 || >=18.0.0"
} }
}, },
"node_modules/@types/body-parser": {
"version": "1.19.5",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
"integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
"dependencies": {
"@types/connect": "*",
"@types/node": "*"
}
},
"node_modules/@types/connect": {
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/express": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
"integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
"dependencies": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^4.17.33",
"@types/qs": "*",
"@types/serve-static": "*"
}
},
"node_modules/@types/express-serve-static-core": {
"version": "4.17.43",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz",
"integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==",
"dependencies": {
"@types/node": "*",
"@types/qs": "*",
"@types/range-parser": "*",
"@types/send": "*"
}
},
"node_modules/@types/http-errors": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA=="
},
"node_modules/@types/jsonwebtoken": { "node_modules/@types/jsonwebtoken": {
"version": "9.0.6", "version": "9.0.6",
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz",
"integrity": "sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==", "integrity": "sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==",
"dev": true,
"dependencies": { "dependencies": {
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/mime": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="
},
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "20.11.19", "version": "20.11.19",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz",
"integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==", "integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==",
"dev": true,
"dependencies": { "dependencies": {
"undici-types": "~5.26.4" "undici-types": "~5.26.4"
} }
}, },
"node_modules/@types/qs": {
"version": "6.9.14",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz",
"integrity": "sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA=="
},
"node_modules/@types/range-parser": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="
},
"node_modules/@types/send": {
"version": "0.17.4",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
"integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
"dependencies": {
"@types/mime": "^1",
"@types/node": "*"
}
},
"node_modules/@types/serve-static": {
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz",
"integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==",
"dependencies": {
"@types/http-errors": "*",
"@types/mime": "*",
"@types/node": "*"
}
},
"node_modules/agent-base": { "node_modules/agent-base": {
"version": "7.1.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
@@ -737,11 +659,6 @@
"balanced-match": "^1.0.0" "balanced-match": "^1.0.0"
} }
}, },
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
},
"node_modules/bytestreamjs": { "node_modules/bytestreamjs": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz",
@@ -850,14 +767,6 @@
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
}, },
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/emoji-regex": { "node_modules/emoji-regex": {
"version": "9.2.2", "version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@@ -1041,7 +950,6 @@
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/jose/-/jose-5.3.0.tgz", "resolved": "https://registry.npmjs.org/jose/-/jose-5.3.0.tgz",
"integrity": "sha512-IChe9AtAE79ru084ow8jzkN2lNrG3Ntfiv65Cvj9uOCE2m5LNsdHG+9EbxWxAoWRF9TgDOqLN5jm08++owDVRg==", "integrity": "sha512-IChe9AtAE79ru084ow8jzkN2lNrG3Ntfiv65Cvj9uOCE2m5LNsdHG+9EbxWxAoWRF9TgDOqLN5jm08++owDVRg==",
"dev": true,
"funding": { "funding": {
"url": "https://github.com/sponsors/panva" "url": "https://github.com/sponsors/panva"
} }
@@ -1057,115 +965,6 @@
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
"dev": true "dev": true
}, },
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
"dependencies": {
"jws": "^3.2.2",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1",
"semver": "^7.5.4"
},
"engines": {
"node": ">=12",
"npm": ">=6"
}
},
"node_modules/jwa": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
"dependencies": {
"buffer-equal-constant-time": "1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/jwks-rsa": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.1.0.tgz",
"integrity": "sha512-v7nqlfezb9YfHHzYII3ef2a2j1XnGeSE/bK3WfumaYCqONAIstJbrEGapz4kadScZzEt7zYCN7bucj8C0Mv/Rg==",
"dependencies": {
"@types/express": "^4.17.17",
"@types/jsonwebtoken": "^9.0.2",
"debug": "^4.3.4",
"jose": "^4.14.6",
"limiter": "^1.1.5",
"lru-memoizer": "^2.2.0"
},
"engines": {
"node": ">=14"
}
},
"node_modules/jwks-rsa/node_modules/jose": {
"version": "4.15.5",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.5.tgz",
"integrity": "sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg==",
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"dependencies": {
"jwa": "^1.4.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/limiter": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz",
"integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA=="
},
"node_modules/lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
},
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
},
"node_modules/lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
},
"node_modules/lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
},
"node_modules/lodash.isnumber": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
},
"node_modules/lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
},
"node_modules/lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
},
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "10.2.2", "version": "10.2.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz",
@@ -1174,29 +973,6 @@
"node": "14 || >=16.14" "node": "14 || >=16.14"
} }
}, },
"node_modules/lru-memoizer": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.2.0.tgz",
"integrity": "sha512-QfOZ6jNkxCcM/BkIPnFsqDhtrazLRsghi9mBwFAzol5GCvj4EkFT899Za3+QwikCg5sRX8JstioBDwOxEyzaNw==",
"dependencies": {
"lodash.clonedeep": "^4.5.0",
"lru-cache": "~4.0.0"
}
},
"node_modules/lru-memoizer/node_modules/lru-cache": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz",
"integrity": "sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==",
"dependencies": {
"pseudomap": "^1.0.1",
"yallist": "^2.0.0"
}
},
"node_modules/lru-memoizer/node_modules/yallist": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A=="
},
"node_modules/make-fetch-happen": { "node_modules/make-fetch-happen": {
"version": "13.0.1", "version": "13.0.1",
"resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz",
@@ -1485,11 +1261,6 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/pseudomap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
"integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ=="
},
"node_modules/pvtsutils": { "node_modules/pvtsutils": {
"version": "1.3.5", "version": "1.3.5",
"resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz",
@@ -1522,25 +1293,6 @@
"node": ">= 4" "node": ">= 4"
} }
}, },
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/safer-buffer": { "node_modules/safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -1833,7 +1585,8 @@
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "5.26.5", "version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
}, },
"node_modules/unique-filename": { "node_modules/unique-filename": {
"version": "3.0.0", "version": "3.0.0",
@@ -2462,100 +2215,24 @@
"promise-retry": "^2.0.1" "promise-retry": "^2.0.1"
} }
}, },
"@types/body-parser": {
"version": "1.19.5",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
"integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
"requires": {
"@types/connect": "*",
"@types/node": "*"
}
},
"@types/connect": {
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
"requires": {
"@types/node": "*"
}
},
"@types/express": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
"integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
"requires": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^4.17.33",
"@types/qs": "*",
"@types/serve-static": "*"
}
},
"@types/express-serve-static-core": {
"version": "4.17.43",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz",
"integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==",
"requires": {
"@types/node": "*",
"@types/qs": "*",
"@types/range-parser": "*",
"@types/send": "*"
}
},
"@types/http-errors": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA=="
},
"@types/jsonwebtoken": { "@types/jsonwebtoken": {
"version": "9.0.6", "version": "9.0.6",
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz",
"integrity": "sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==", "integrity": "sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==",
"dev": true,
"requires": { "requires": {
"@types/node": "*" "@types/node": "*"
} }
}, },
"@types/mime": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="
},
"@types/node": { "@types/node": {
"version": "20.11.19", "version": "20.11.19",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz",
"integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==", "integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==",
"dev": true,
"requires": { "requires": {
"undici-types": "~5.26.4" "undici-types": "~5.26.4"
} }
}, },
"@types/qs": {
"version": "6.9.14",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz",
"integrity": "sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA=="
},
"@types/range-parser": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="
},
"@types/send": {
"version": "0.17.4",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
"integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
"requires": {
"@types/mime": "^1",
"@types/node": "*"
}
},
"@types/serve-static": {
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz",
"integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==",
"requires": {
"@types/http-errors": "*",
"@types/mime": "*",
"@types/node": "*"
}
},
"agent-base": { "agent-base": {
"version": "7.1.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
@@ -2617,11 +2294,6 @@
"balanced-match": "^1.0.0" "balanced-match": "^1.0.0"
} }
}, },
"buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
},
"bytestreamjs": { "bytestreamjs": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz",
@@ -2704,14 +2376,6 @@
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
}, },
"ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"requires": {
"safe-buffer": "^5.0.1"
}
},
"emoji-regex": { "emoji-regex": {
"version": "9.2.2", "version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@@ -2844,8 +2508,7 @@
"jose": { "jose": {
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/jose/-/jose-5.3.0.tgz", "resolved": "https://registry.npmjs.org/jose/-/jose-5.3.0.tgz",
"integrity": "sha512-IChe9AtAE79ru084ow8jzkN2lNrG3Ntfiv65Cvj9uOCE2m5LNsdHG+9EbxWxAoWRF9TgDOqLN5jm08++owDVRg==", "integrity": "sha512-IChe9AtAE79ru084ow8jzkN2lNrG3Ntfiv65Cvj9uOCE2m5LNsdHG+9EbxWxAoWRF9TgDOqLN5jm08++owDVRg=="
"dev": true
}, },
"jsbn": { "jsbn": {
"version": "1.1.0", "version": "1.1.0",
@@ -2858,137 +2521,11 @@
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
"dev": true "dev": true
}, },
"jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
"requires": {
"jws": "^3.2.2",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1",
"semver": "^7.5.4"
}
},
"jwa": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
"requires": {
"buffer-equal-constant-time": "1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"jwks-rsa": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.1.0.tgz",
"integrity": "sha512-v7nqlfezb9YfHHzYII3ef2a2j1XnGeSE/bK3WfumaYCqONAIstJbrEGapz4kadScZzEt7zYCN7bucj8C0Mv/Rg==",
"requires": {
"@types/express": "^4.17.17",
"@types/jsonwebtoken": "^9.0.2",
"debug": "^4.3.4",
"jose": "^4.14.6",
"limiter": "^1.1.5",
"lru-memoizer": "^2.2.0"
},
"dependencies": {
"jose": {
"version": "4.15.5",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.5.tgz",
"integrity": "sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg=="
}
}
},
"jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"requires": {
"jwa": "^1.4.1",
"safe-buffer": "^5.0.1"
}
},
"limiter": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz",
"integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA=="
},
"lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
},
"lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
},
"lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
},
"lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
},
"lodash.isnumber": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
},
"lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
},
"lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
},
"lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
},
"lru-cache": { "lru-cache": {
"version": "10.2.2", "version": "10.2.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz",
"integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==" "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ=="
}, },
"lru-memoizer": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.2.0.tgz",
"integrity": "sha512-QfOZ6jNkxCcM/BkIPnFsqDhtrazLRsghi9mBwFAzol5GCvj4EkFT899Za3+QwikCg5sRX8JstioBDwOxEyzaNw==",
"requires": {
"lodash.clonedeep": "^4.5.0",
"lru-cache": "~4.0.0"
},
"dependencies": {
"lru-cache": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz",
"integrity": "sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==",
"requires": {
"pseudomap": "^1.0.1",
"yallist": "^2.0.0"
}
},
"yallist": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A=="
}
}
},
"make-fetch-happen": { "make-fetch-happen": {
"version": "13.0.1", "version": "13.0.1",
"resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz",
@@ -3202,11 +2739,6 @@
"integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==",
"dev": true "dev": true
}, },
"pseudomap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
"integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ=="
},
"pvtsutils": { "pvtsutils": {
"version": "1.3.5", "version": "1.3.5",
"resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz",
@@ -3233,11 +2765,6 @@
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
"integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==" "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="
}, },
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
},
"safer-buffer": { "safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -3457,7 +2984,8 @@
"undici-types": { "undici-types": {
"version": "5.26.5", "version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
}, },
"unique-filename": { "unique-filename": {
"version": "3.0.0", "version": "3.0.0",
+2 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@actions/attest", "name": "@actions/attest",
"version": "1.3.0", "version": "1.4.0",
"description": "Actions attestation lib", "description": "Actions attestation lib",
"keywords": [ "keywords": [
"github", "github",
@@ -38,7 +38,6 @@
"@sigstore/mock": "^0.7.4", "@sigstore/mock": "^0.7.4",
"@sigstore/rekor-types": "^2.0.0", "@sigstore/rekor-types": "^2.0.0",
"@types/jsonwebtoken": "^9.0.6", "@types/jsonwebtoken": "^9.0.6",
"jose": "^5.2.3",
"nock": "^13.5.1", "nock": "^13.5.1",
"undici": "^5.28.4" "undici": "^5.28.4"
}, },
@@ -49,8 +48,7 @@
"@octokit/plugin-retry": "^6.0.1", "@octokit/plugin-retry": "^6.0.1",
"@sigstore/bundle": "^2.3.2", "@sigstore/bundle": "^2.3.2",
"@sigstore/sign": "^2.3.2", "@sigstore/sign": "^2.3.2",
"jsonwebtoken": "^9.0.2", "jose": "^5.2.3"
"jwks-rsa": "^3.1.0"
}, },
"overrides": { "overrides": {
"@octokit/plugin-retry": { "@octokit/plugin-retry": {
+7 -1
View File
@@ -28,6 +28,8 @@ export type AttestOptions = {
// Sigstore instance to use for signing. Must be one of "public-good" or // Sigstore instance to use for signing. Must be one of "public-good" or
// "github". // "github".
sigstore?: SigstoreInstance sigstore?: SigstoreInstance
// HTTP headers to include in request to attestations API.
headers?: {[header: string]: string | number | undefined}
// Whether to skip writing the attestation to the GH attestations API. // Whether to skip writing the attestation to the GH attestations API.
skipWrite?: boolean skipWrite?: boolean
} }
@@ -61,7 +63,11 @@ export async function attest(options: AttestOptions): Promise<Attestation> {
// Store the attestation // Store the attestation
let attestationID: string | undefined let attestationID: string | undefined
if (options.skipWrite !== true) { if (options.skipWrite !== true) {
attestationID = await writeAttestation(bundleToJSON(bundle), options.token) attestationID = await writeAttestation(
bundleToJSON(bundle),
options.token,
{headers: options.headers}
)
} }
return toAttestation(bundle, attestationID) return toAttestation(bundle, attestationID)
+51 -46
View File
@@ -1,10 +1,14 @@
import {getIDToken} from '@actions/core' import {getIDToken} from '@actions/core'
import {HttpClient} from '@actions/http-client' import {HttpClient} from '@actions/http-client'
import * as jwt from 'jsonwebtoken' import * as jose from 'jose'
import jwks from 'jwks-rsa'
const OIDC_AUDIENCE = 'nobody' const OIDC_AUDIENCE = 'nobody'
const VALID_SERVER_URLS = [
'https://github.com',
new RegExp('^https://[a-z0-9-]+\\.ghe\\.com$')
] as const
const REQUIRED_CLAIMS = [ const REQUIRED_CLAIMS = [
'iss', 'iss',
'ref', 'ref',
@@ -26,7 +30,8 @@ type OIDCConfig = {
jwks_uri: string jwks_uri: string
} }
export const getIDTokenClaims = async (issuer: string): Promise<ClaimSet> => { export const getIDTokenClaims = async (issuer?: string): Promise<ClaimSet> => {
issuer = issuer || getIssuer()
try { try {
const token = await getIDToken(OIDC_AUDIENCE) const token = await getIDToken(OIDC_AUDIENCE)
const claims = await decodeOIDCToken(token, issuer) const claims = await decodeOIDCToken(token, issuer)
@@ -40,55 +45,37 @@ export const getIDTokenClaims = async (issuer: string): Promise<ClaimSet> => {
const decodeOIDCToken = async ( const decodeOIDCToken = async (
token: string, token: string,
issuer: string issuer: string
): Promise<jwt.JwtPayload> => { ): Promise<jose.JWTPayload> => {
// Verify and decode token // Verify and decode token
return new Promise((resolve, reject) => { const jwks = jose.createLocalJWKSet(await getJWKS(issuer))
jwt.verify( const {payload} = await jose.jwtVerify(token, jwks, {
token, audience: OIDC_AUDIENCE,
getPublicKey(issuer), issuer
{audience: OIDC_AUDIENCE, issuer},
(err, decoded) => {
if (err) {
reject(err)
} else if (!decoded || typeof decoded === 'string') {
reject(new Error('No decoded token'))
} else {
resolve(decoded)
}
}
)
}) })
return payload
} }
// Returns a callback to locate the public key for the given JWT header. This const getJWKS = async (issuer: string): Promise<jose.JSONWebKeySet> => {
// involves two calls: const client = new HttpClient('@actions/attest')
// 1. Fetch the OpenID configuration to get the JWKS URI. const config = await client.getJson<OIDCConfig>(
// 2. Fetch the public key from the JWKS URI. `${issuer}/.well-known/openid-configuration`
const getPublicKey = )
(issuer: string): jwt.GetPublicKeyOrSecret =>
(header: jwt.JwtHeader, callback: jwt.SigningKeyCallback) => { if (!config.result) {
// Look up the JWKS URI from the issuer's OpenID configuration throw new Error('No OpenID configuration found')
new HttpClient('actions/attest')
.getJson<OIDCConfig>(`${issuer}/.well-known/openid-configuration`)
.then(data => {
if (!data.result) {
callback(new Error('No OpenID configuration found'))
} else {
// Fetch the public key from the JWKS URI
jwks({jwksUri: data.result.jwks_uri}).getSigningKey(
header.kid,
(err, key) => {
callback(err, key?.getPublicKey())
}
)
}
})
.catch(err => {
callback(err)
})
} }
function assertClaimSet(claims: jwt.JwtPayload): asserts claims is ClaimSet { const jwks = await client.getJson<jose.JSONWebKeySet>(config.result.jwks_uri)
if (!jwks.result) {
throw new Error('No JWKS found for issuer')
}
return jwks.result
}
function assertClaimSet(claims: jose.JWTPayload): asserts claims is ClaimSet {
const missingClaims: string[] = [] const missingClaims: string[] = []
for (const claim of REQUIRED_CLAIMS) { for (const claim of REQUIRED_CLAIMS) {
@@ -101,3 +88,21 @@ function assertClaimSet(claims: jwt.JwtPayload): asserts claims is ClaimSet {
throw new Error(`Missing claims: ${missingClaims.join(', ')}`) throw new Error(`Missing claims: ${missingClaims.join(', ')}`)
} }
} }
// Derive the current OIDC issuer based on the server URL
function getIssuer(): string {
const serverURL = process.env.GITHUB_SERVER_URL || 'https://github.com'
// Ensure the server URL is a valid GitHub server URL
if (!VALID_SERVER_URLS.some(valid_url => serverURL.match(valid_url))) {
throw new Error(`Invalid server URL: ${serverURL}`)
}
let host = new URL(serverURL).hostname
if (host === 'github.com') {
host = 'githubusercontent.com'
}
return `https://token.actions.${host}`
}
+1 -3
View File
@@ -5,8 +5,6 @@ import type {Attestation, Predicate} from './shared.types'
const SLSA_PREDICATE_V1_TYPE = 'https://slsa.dev/provenance/v1' const SLSA_PREDICATE_V1_TYPE = 'https://slsa.dev/provenance/v1'
const GITHUB_BUILD_TYPE = 'https://actions.github.io/buildtypes/workflow/v1' const GITHUB_BUILD_TYPE = 'https://actions.github.io/buildtypes/workflow/v1'
const DEFAULT_ISSUER = 'https://token.actions.githubusercontent.com'
export type AttestProvenanceOptions = Omit< export type AttestProvenanceOptions = Omit<
AttestOptions, AttestOptions,
'predicate' | 'predicateType' 'predicate' | 'predicateType'
@@ -24,7 +22,7 @@ export type AttestProvenanceOptions = Omit<
* @returns The SLSA provenance predicate. * @returns The SLSA provenance predicate.
*/ */
export const buildSLSAProvenancePredicate = async ( export const buildSLSAProvenancePredicate = async (
issuer: string = DEFAULT_ISSUER issuer?: string
): Promise<Predicate> => { ): Promise<Predicate> => {
const serverURL = process.env.GITHUB_SERVER_URL const serverURL = process.env.GITHUB_SERVER_URL
const claims = await getIDTokenClaims(issuer) const claims = await getIDTokenClaims(issuer)
+3
View File
@@ -1,11 +1,13 @@
import * as github from '@actions/github' import * as github from '@actions/github'
import {retry} from '@octokit/plugin-retry' import {retry} from '@octokit/plugin-retry'
import {RequestHeaders} from '@octokit/types'
const CREATE_ATTESTATION_REQUEST = 'POST /repos/{owner}/{repo}/attestations' const CREATE_ATTESTATION_REQUEST = 'POST /repos/{owner}/{repo}/attestations'
const DEFAULT_RETRY_COUNT = 5 const DEFAULT_RETRY_COUNT = 5
export type WriteOptions = { export type WriteOptions = {
retry?: number retry?: number
headers?: RequestHeaders
} }
/** /**
* Writes an attestation to the repository's attestations endpoint. * Writes an attestation to the repository's attestations endpoint.
@@ -26,6 +28,7 @@ export const writeAttestation = async (
const response = await octokit.request(CREATE_ATTESTATION_REQUEST, { const response = await octokit.request(CREATE_ATTESTATION_REQUEST, {
owner: github.context.repo.owner, owner: github.context.repo.owner,
repo: github.context.repo.repo, repo: github.context.repo.repo,
headers: options.headers,
data: {bundle: attestation} data: {bundle: attestation}
}) })
+3
View File
@@ -1,5 +1,8 @@
# @actions/glob Releases # @actions/glob Releases
### 0.5.0
- Added `excludeHiddenFiles` option, which is disabled by default to preserve existing behavior [#1791: Add glob option to ignore hidden files](https://github.com/actions/toolkit/pull/1791)
### 0.4.0 ### 0.4.0
- Pass in the current workspace as a parameter to HashFiles [#1318](https://github.com/actions/toolkit/pull/1318) - Pass in the current workspace as a parameter to HashFiles [#1318](https://github.com/actions/toolkit/pull/1318)
@@ -708,7 +708,7 @@ describe('globber', () => {
expect(itemPaths).toEqual([]) expect(itemPaths).toEqual([])
}) })
it('returns hidden files', async () => { it('returns hidden files by default', async () => {
// Create the following layout: // Create the following layout:
// <root> // <root>
// <root>/.emptyFolder // <root>/.emptyFolder
@@ -734,6 +734,26 @@ describe('globber', () => {
]) ])
}) })
it('ignores hidden files when excludeHiddenFiles is set', async () => {
// Create the following layout:
// <root>
// <root>/.emptyFolder
// <root>/.file
// <root>/.folder
// <root>/.folder/file
const root = path.join(getTestTemp(), 'ignores-hidden-files')
await createHiddenDirectory(path.join(root, '.emptyFolder'))
await createHiddenDirectory(path.join(root, '.folder'))
await createHiddenFile(path.join(root, '.file'), 'test .file content')
await fs.writeFile(
path.join(root, '.folder', 'file'),
'test .folder/file content'
)
const itemPaths = await glob(root, {excludeHiddenFiles: true})
expect(itemPaths).toEqual([root])
})
it('returns normalized paths', async () => { it('returns normalized paths', async () => {
// Create the following layout: // Create the following layout:
// <root>/hello/world.txt // <root>/hello/world.txt
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@actions/glob", "name": "@actions/glob",
"version": "0.4.0", "version": "0.5.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"description": "Actions glob lib", "description": "Actions glob lib",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@actions/glob", "name": "@actions/glob",
"version": "0.4.0", "version": "0.5.0",
"preview": true, "preview": true,
"description": "Actions glob lib", "description": "Actions glob lib",
"keywords": [ "keywords": [
@@ -9,7 +9,8 @@ export function getOptions(copy?: GlobOptions): GlobOptions {
followSymbolicLinks: true, followSymbolicLinks: true,
implicitDescendants: true, implicitDescendants: true,
matchDirectories: true, matchDirectories: true,
omitBrokenSymbolicLinks: true omitBrokenSymbolicLinks: true,
excludeHiddenFiles: false
} }
if (copy) { if (copy) {
@@ -32,6 +33,11 @@ export function getOptions(copy?: GlobOptions): GlobOptions {
result.omitBrokenSymbolicLinks = copy.omitBrokenSymbolicLinks result.omitBrokenSymbolicLinks = copy.omitBrokenSymbolicLinks
core.debug(`omitBrokenSymbolicLinks '${result.omitBrokenSymbolicLinks}'`) core.debug(`omitBrokenSymbolicLinks '${result.omitBrokenSymbolicLinks}'`)
} }
if (typeof copy.excludeHiddenFiles === 'boolean') {
result.excludeHiddenFiles = copy.excludeHiddenFiles
core.debug(`excludeHiddenFiles '${result.excludeHiddenFiles}'`)
}
} }
return result return result
@@ -36,4 +36,13 @@ export interface GlobOptions {
* @default true * @default true
*/ */
omitBrokenSymbolicLinks?: boolean omitBrokenSymbolicLinks?: boolean
/**
* Indicates whether to exclude hidden files (files and directories starting with a `.`).
* This does not apply to Windows files and directories with the hidden attribute unless
* they are also prefixed with a `.`.
*
* @default false
*/
excludeHiddenFiles?: boolean
} }
+5
View File
@@ -128,6 +128,11 @@ export class DefaultGlobber implements Globber {
continue continue
} }
// Hidden file or directory?
if (options.excludeHiddenFiles && path.basename(item.path).match(/^\./)) {
continue
}
// Directory // Directory
if (stats.isDirectory()) { if (stats.isDirectory()) {
// Matched // Matched
+9
View File
@@ -1,5 +1,14 @@
## Releases ## Releases
## 2.2.3
- Fixed an issue where proxy username and password were not handled correctly [#1799](https://github.com/actions/toolkit/pull/1799)
## 2.2.2
- Better handling of url encoded usernames and passwords in proxy config [#1782](https://github.com/actions/toolkit/pull/1782)
## 2.2.1
- Make sure RequestOptions.keepAlive is applied properly on node20 runtime [#1572](https://github.com/actions/toolkit/pull/1572)
## 2.2.0 ## 2.2.0
- Add function to return proxy agent dispatcher for compatibility with latest octokit packages [#1547](https://github.com/actions/toolkit/pull/1547) - Add function to return proxy agent dispatcher for compatibility with latest octokit packages [#1547](https://github.com/actions/toolkit/pull/1547)
+32 -31
View File
@@ -37,7 +37,7 @@ describe('basics', () => {
// "user-agent": "typed-test-client-tests" // "user-agent": "typed-test-client-tests"
// }, // },
// "origin": "173.95.152.44", // "origin": "173.95.152.44",
// "url": "https://postman-echo.com/get" // "url": "http://postman-echo.com/get"
// } // }
it('does basic http get request', async () => { it('does basic http get request', async () => {
@@ -63,16 +63,17 @@ describe('basics', () => {
expect(obj.headers['user-agent']).toBeFalsy() expect(obj.headers['user-agent']).toBeFalsy()
}) })
/* TODO write a mock rather then relying on a third party
it('does basic https get request', async () => { it('does basic https get request', async () => {
const res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
'https://postman-echo.com/get' 'http://postman-echo.com/get'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
const body: string = await res.readBody() const body: string = await res.readBody()
const obj = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('https://postman-echo.com/get') expect(obj.url).toBe('http://postman-echo.com/get')
}) })
*/
it('does basic http get request with default headers', async () => { it('does basic http get request with default headers', async () => {
const http: httpm.HttpClient = new httpm.HttpClient( const http: httpm.HttpClient = new httpm.HttpClient(
'http-client-tests', 'http-client-tests',
@@ -125,12 +126,12 @@ describe('basics', () => {
it('pipes a get request', async () => { it('pipes a get request', async () => {
return new Promise<void>(async resolve => { return new Promise<void>(async resolve => {
const file = fs.createWriteStream(sampleFilePath) const file = fs.createWriteStream(sampleFilePath)
;(await _http.get('https://postman-echo.com/get')).message ;(await _http.get('http://postman-echo.com/get')).message
.pipe(file) .pipe(file)
.on('close', () => { .on('close', () => {
const body: string = fs.readFileSync(sampleFilePath).toString() const body: string = fs.readFileSync(sampleFilePath).toString()
const obj = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('https://postman-echo.com/get') expect(obj.url).toBe('http://postman-echo.com/get')
resolve() resolve()
}) })
}) })
@@ -138,32 +139,32 @@ describe('basics', () => {
it('does basic get request with redirects', async () => { it('does basic get request with redirects', async () => {
const res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
`https://postman-echo.com/redirect-to?url=${encodeURIComponent( `http://postman-echo.com/redirect-to?url=${encodeURIComponent(
'https://postman-echo.com/get' 'http://postman-echo.com/get'
)}` )}`
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
const body: string = await res.readBody() const body: string = await res.readBody()
const obj = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('https://postman-echo.com/get') expect(obj.url).toBe('http://postman-echo.com/get')
}) })
it('does basic get request with redirects (303)', async () => { it('does basic get request with redirects (303)', async () => {
const res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
`https://postman-echo.com/redirect-to?url=${encodeURIComponent( `http://postman-echo.com/redirect-to?url=${encodeURIComponent(
'https://postman-echo.com/get' 'http://postman-echo.com/get'
)}&status_code=303` )}&status_code=303`
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
const body: string = await res.readBody() const body: string = await res.readBody()
const obj = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('https://postman-echo.com/get') expect(obj.url).toBe('http://postman-echo.com/get')
}) })
it('returns 404 for not found get request on redirect', async () => { it('returns 404 for not found get request on redirect', async () => {
const res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
`https://postman-echo.com/redirect-to?url=${encodeURIComponent( `http://postman-echo.com/redirect-to?url=${encodeURIComponent(
'https://postman-echo.com/status/404' 'http://postman-echo.com/status/404'
)}&status_code=303` )}&status_code=303`
) )
expect(res.message.statusCode).toBe(404) expect(res.message.statusCode).toBe(404)
@@ -177,8 +178,8 @@ describe('basics', () => {
{allowRedirects: false} {allowRedirects: false}
) )
const res: httpm.HttpClientResponse = await http.get( const res: httpm.HttpClientResponse = await http.get(
`https://postman-echo.com/redirect-to?url=${encodeURIComponent( `http://postman-echo.com/redirect-to?url=${encodeURIComponent(
'https://postman-echo.com/get' 'http://postman-echo.com/get'
)}` )}`
) )
expect(res.message.statusCode).toBe(302) expect(res.message.statusCode).toBe(302)
@@ -191,8 +192,8 @@ describe('basics', () => {
authorization: 'shhh' authorization: 'shhh'
} }
const res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
`https://postman-echo.com/redirect-to?url=${encodeURIComponent( `http://postman-echo.com/redirect-to?url=${encodeURIComponent(
'https://www.postman-echo.com/get' 'http://www.postman-echo.com/get'
)}`, )}`,
headers headers
) )
@@ -204,7 +205,7 @@ describe('basics', () => {
expect(obj.headers[httpm.Headers.Accept]).toBe('application/json') expect(obj.headers[httpm.Headers.Accept]).toBe('application/json')
expect(obj.headers['Authorization']).toBeUndefined() expect(obj.headers['Authorization']).toBeUndefined()
expect(obj.headers['authorization']).toBeUndefined() expect(obj.headers['authorization']).toBeUndefined()
expect(obj.url).toBe('https://www.postman-echo.com/get') expect(obj.url).toBe('http://www.postman-echo.com/get')
}) })
it('does not pass Auth with diff hostname redirects', async () => { it('does not pass Auth with diff hostname redirects', async () => {
@@ -213,8 +214,8 @@ describe('basics', () => {
Authorization: 'shhh' Authorization: 'shhh'
} }
const res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
`https://postman-echo.com/redirect-to?url=${encodeURIComponent( `http://postman-echo.com/redirect-to?url=${encodeURIComponent(
'https://www.postman-echo.com/get' 'http://www.postman-echo.com/get'
)}`, )}`,
headers headers
) )
@@ -226,7 +227,7 @@ describe('basics', () => {
expect(obj.headers[httpm.Headers.Accept]).toBe('application/json') expect(obj.headers[httpm.Headers.Accept]).toBe('application/json')
expect(obj.headers['Authorization']).toBeUndefined() expect(obj.headers['Authorization']).toBeUndefined()
expect(obj.headers['authorization']).toBeUndefined() expect(obj.headers['authorization']).toBeUndefined()
expect(obj.url).toBe('https://www.postman-echo.com/get') expect(obj.url).toBe('http://www.postman-echo.com/get')
}) })
it('does basic head request', async () => { it('does basic head request', async () => {
@@ -289,11 +290,11 @@ describe('basics', () => {
it('gets a json object', async () => { it('gets a json object', async () => {
const jsonObj = await _http.getJson<HttpBinData>( const jsonObj = await _http.getJson<HttpBinData>(
'https://postman-echo.com/get' 'http://postman-echo.com/get'
) )
expect(jsonObj.statusCode).toBe(200) expect(jsonObj.statusCode).toBe(200)
expect(jsonObj.result).toBeDefined() expect(jsonObj.result).toBeDefined()
expect(jsonObj.result?.url).toBe('https://postman-echo.com/get') expect(jsonObj.result?.url).toBe('http://postman-echo.com/get')
expect(jsonObj.result?.headers[httpm.Headers.Accept]).toBe( expect(jsonObj.result?.headers[httpm.Headers.Accept]).toBe(
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
) )
@@ -304,7 +305,7 @@ describe('basics', () => {
it('getting a non existent json object returns null', async () => { it('getting a non existent json object returns null', async () => {
const jsonObj = await _http.getJson<HttpBinData>( const jsonObj = await _http.getJson<HttpBinData>(
'https://postman-echo.com/status/404' 'http://postman-echo.com/status/404'
) )
expect(jsonObj.statusCode).toBe(404) expect(jsonObj.statusCode).toBe(404)
expect(jsonObj.result).toBeNull() expect(jsonObj.result).toBeNull()
@@ -313,12 +314,12 @@ describe('basics', () => {
it('posts a json object', async () => { it('posts a json object', async () => {
const res = {name: 'foo'} const res = {name: 'foo'}
const restRes = await _http.postJson<HttpBinData>( const restRes = await _http.postJson<HttpBinData>(
'https://postman-echo.com/post', 'http://postman-echo.com/post',
res res
) )
expect(restRes.statusCode).toBe(200) expect(restRes.statusCode).toBe(200)
expect(restRes.result).toBeDefined() expect(restRes.result).toBeDefined()
expect(restRes.result?.url).toBe('https://postman-echo.com/post') expect(restRes.result?.url).toBe('http://postman-echo.com/post')
expect(restRes.result?.json.name).toBe('foo') expect(restRes.result?.json.name).toBe('foo')
expect(restRes.result?.headers[httpm.Headers.Accept]).toBe( expect(restRes.result?.headers[httpm.Headers.Accept]).toBe(
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
@@ -334,12 +335,12 @@ describe('basics', () => {
it('puts a json object', async () => { it('puts a json object', async () => {
const res = {name: 'foo'} const res = {name: 'foo'}
const restRes = await _http.putJson<HttpBinData>( const restRes = await _http.putJson<HttpBinData>(
'https://postman-echo.com/put', 'http://postman-echo.com/put',
res res
) )
expect(restRes.statusCode).toBe(200) expect(restRes.statusCode).toBe(200)
expect(restRes.result).toBeDefined() expect(restRes.result).toBeDefined()
expect(restRes.result?.url).toBe('https://postman-echo.com/put') expect(restRes.result?.url).toBe('http://postman-echo.com/put')
expect(restRes.result?.json.name).toBe('foo') expect(restRes.result?.json.name).toBe('foo')
expect(restRes.result?.headers[httpm.Headers.Accept]).toBe( expect(restRes.result?.headers[httpm.Headers.Accept]).toBe(
@@ -356,12 +357,12 @@ describe('basics', () => {
it('patch a json object', async () => { it('patch a json object', async () => {
const res = {name: 'foo'} const res = {name: 'foo'}
const restRes = await _http.patchJson<HttpBinData>( const restRes = await _http.patchJson<HttpBinData>(
'https://postman-echo.com/patch', 'http://postman-echo.com/patch',
res res
) )
expect(restRes.statusCode).toBe(200) expect(restRes.statusCode).toBe(200)
expect(restRes.result).toBeDefined() expect(restRes.result).toBeDefined()
expect(restRes.result?.url).toBe('https://postman-echo.com/patch') expect(restRes.result?.url).toBe('http://postman-echo.com/patch')
expect(restRes.result?.json.name).toBe('foo') expect(restRes.result?.json.name).toBe('foo')
expect(restRes.result?.headers[httpm.Headers.Accept]).toBe( expect(restRes.result?.headers[httpm.Headers.Accept]).toBe(
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
@@ -12,7 +12,7 @@ describe('headers', () => {
it('preserves existing headers on getJson', async () => { it('preserves existing headers on getJson', async () => {
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'} const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj = await _http.getJson<any>( let jsonObj = await _http.getJson<any>(
'https://postman-echo.com/get', 'http://postman-echo.com/get',
additionalHeaders additionalHeaders
) )
expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('foo') expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('foo')
@@ -26,7 +26,7 @@ describe('headers', () => {
[httpm.Headers.Accept]: 'baz' [httpm.Headers.Accept]: 'baz'
} }
} }
jsonObj = await httpWithHeaders.getJson<any>('https://postman-echo.com/get') jsonObj = await httpWithHeaders.getJson<any>('http://postman-echo.com/get')
expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz') expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz')
expect(jsonObj.headers[httpm.Headers.ContentType]).toContain( expect(jsonObj.headers[httpm.Headers.ContentType]).toContain(
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
@@ -36,7 +36,7 @@ describe('headers', () => {
it('preserves existing headers on postJson', async () => { it('preserves existing headers on postJson', async () => {
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'} const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj = await _http.postJson<any>( let jsonObj = await _http.postJson<any>(
'https://postman-echo.com/post', 'http://postman-echo.com/post',
{}, {},
additionalHeaders additionalHeaders
) )
@@ -52,7 +52,7 @@ describe('headers', () => {
} }
} }
jsonObj = await httpWithHeaders.postJson<any>( jsonObj = await httpWithHeaders.postJson<any>(
'https://postman-echo.com/post', 'http://postman-echo.com/post',
{} {}
) )
expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz') expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz')
@@ -64,7 +64,7 @@ describe('headers', () => {
it('preserves existing headers on putJson', async () => { it('preserves existing headers on putJson', async () => {
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'} const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj = await _http.putJson<any>( let jsonObj = await _http.putJson<any>(
'https://postman-echo.com/put', 'http://postman-echo.com/put',
{}, {},
additionalHeaders additionalHeaders
) )
@@ -80,7 +80,7 @@ describe('headers', () => {
} }
} }
jsonObj = await httpWithHeaders.putJson<any>( jsonObj = await httpWithHeaders.putJson<any>(
'https://postman-echo.com/put', 'http://postman-echo.com/put',
{} {}
) )
expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz') expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz')
@@ -92,7 +92,7 @@ describe('headers', () => {
it('preserves existing headers on patchJson', async () => { it('preserves existing headers on patchJson', async () => {
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'} const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj = await _http.patchJson<any>( let jsonObj = await _http.patchJson<any>(
'https://postman-echo.com/patch', 'http://postman-echo.com/patch',
{}, {},
additionalHeaders additionalHeaders
) )
@@ -108,7 +108,7 @@ describe('headers', () => {
} }
} }
jsonObj = await httpWithHeaders.patchJson<any>( jsonObj = await httpWithHeaders.patchJson<any>(
'https://postman-echo.com/patch', 'http://postman-echo.com/patch',
{} {}
) )
expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz') expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz')
+21 -6
View File
@@ -222,30 +222,33 @@ describe('proxy', () => {
expect(_proxyConnects).toHaveLength(0) expect(_proxyConnects).toHaveLength(0)
}) })
// TODO mock this out so we don't rely on a third party
/*
it('HttpClient does basic https get request through proxy', async () => { it('HttpClient does basic https get request through proxy', async () => {
process.env['https_proxy'] = _proxyUrl process.env['https_proxy'] = _proxyUrl
const httpClient = new httpm.HttpClient() const httpClient = new httpm.HttpClient()
const res: httpm.HttpClientResponse = await httpClient.get( const res: httpm.HttpClientResponse = await httpClient.get(
'https://postman-echo.com/get' 'http://postman-echo.com/get'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
const body: string = await res.readBody() const body: string = await res.readBody()
const obj = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('https://postman-echo.com/get') expect(obj.url).toBe('http://postman-echo.com/get')
expect(_proxyConnects).toEqual(['postman-echo.com:443']) expect(_proxyConnects).toEqual(['postman-echo.com:443'])
}) })
*/
it('HttpClient does basic https get request when bypass proxy', async () => { it('HttpClient does basic http get request when bypass proxy', async () => {
process.env['https_proxy'] = _proxyUrl process.env['http_proxy'] = _proxyUrl
process.env['no_proxy'] = 'postman-echo.com' process.env['no_proxy'] = 'postman-echo.com'
const httpClient = new httpm.HttpClient() const httpClient = new httpm.HttpClient()
const res: httpm.HttpClientResponse = await httpClient.get( const res: httpm.HttpClientResponse = await httpClient.get(
'https://postman-echo.com/get' 'http://postman-echo.com/get'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
const body: string = await res.readBody() const body: string = await res.readBody()
const obj = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('https://postman-echo.com/get') expect(obj.url).toBe('http://postman-echo.com/get')
expect(_proxyConnects).toHaveLength(0) expect(_proxyConnects).toHaveLength(0)
}) })
@@ -304,6 +307,18 @@ describe('proxy', () => {
console.log(agent) console.log(agent)
expect(agent instanceof ProxyAgent).toBe(true) expect(agent instanceof ProxyAgent).toBe(true)
}) })
it('proxyAuth is set in tunnel agent when authentication is provided with URIencoding', async () => {
process.env['https_proxy'] =
'http://user%40github.com:p%[email protected]:8080'
const httpClient = new httpm.HttpClient()
const agent: any = httpClient.getAgent('https://some-url')
// eslint-disable-next-line no-console
console.log(agent)
expect(agent.proxyOptions.host).toBe('127.0.0.1')
expect(agent.proxyOptions.port).toBe('8080')
expect(agent.proxyOptions.proxyAuth).toBe('[email protected]:p@ssword')
})
}) })
function _clearVars(): void { function _clearVars(): void {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@actions/http-client", "name": "@actions/http-client",
"version": "2.2.1", "version": "2.2.3",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@actions/http-client", "name": "@actions/http-client",
"version": "2.2.1", "version": "2.2.3",
"description": "Actions Http Client", "description": "Actions Http Client",
"keywords": [ "keywords": [
"github", "github",
+3 -1
View File
@@ -726,7 +726,9 @@ export class HttpClient {
uri: proxyUrl.href, uri: proxyUrl.href,
pipelining: !this._keepAlive ? 0 : 1, pipelining: !this._keepAlive ? 0 : 1,
...((proxyUrl.username || proxyUrl.password) && { ...((proxyUrl.username || proxyUrl.password) && {
token: `${proxyUrl.username}:${proxyUrl.password}` token: `Basic ${Buffer.from(
`${proxyUrl.username}:${proxyUrl.password}`
).toString('base64')}`
}) })
}) })
this._proxyAgentDispatcher = proxyAgent this._proxyAgentDispatcher = proxyAgent
+21 -2
View File
@@ -15,10 +15,10 @@ export function getProxyUrl(reqUrl: URL): URL | undefined {
if (proxyVar) { if (proxyVar) {
try { try {
return new URL(proxyVar) return new DecodedURL(proxyVar)
} catch { } catch {
if (!proxyVar.startsWith('http://') && !proxyVar.startsWith('https://')) if (!proxyVar.startsWith('http://') && !proxyVar.startsWith('https://'))
return new URL(`http://${proxyVar}`) return new DecodedURL(`http://${proxyVar}`)
} }
} else { } else {
return undefined return undefined
@@ -87,3 +87,22 @@ function isLoopbackAddress(host: string): boolean {
hostLower.startsWith('[0:0:0:0:0:0:0:1]') hostLower.startsWith('[0:0:0:0:0:0:0:1]')
) )
} }
class DecodedURL extends URL {
private _decodedUsername: string
private _decodedPassword: string
constructor(url: string | URL, base?: string | URL) {
super(url, base)
this._decodedUsername = decodeURIComponent(super.username)
this._decodedPassword = decodeURIComponent(super.password)
}
get username(): string {
return this._decodedUsername
}
get password(): string {
return this._decodedPassword
}
}