From 21433fba66ba4b0617d75c75f366b2c2247a0b4d Mon Sep 17 00:00:00 2001 From: Remco Vermeulen Date: Wed, 21 Aug 2024 15:18:40 -0700 Subject: [PATCH 1/3] Add permission input for scoped tokens --- action.yml | 3 + dist/main.cjs | 296 +++++++++++++++++++++++++------------------------- dist/post.cjs | 229 ++++++++++++++++++-------------------- lib/main.js | 47 ++++++-- main.js | 5 +- 5 files changed, 299 insertions(+), 281 deletions(-) diff --git a/action.yml b/action.yml index 09cc8fa..3e74c81 100644 --- a/action.yml +++ b/action.yml @@ -37,6 +37,9 @@ inputs: github-api-url: description: The URL of the GitHub REST API. default: ${{ github.api_url }} + permissions: + description: "A comma or newline seperated list of permissions to scope the token." + required: false outputs: token: description: "GitHub installation access token" diff --git a/dist/main.cjs b/dist/main.cjs index 474eaef..aa782ce 100644 --- a/dist/main.cjs +++ b/dist/main.cjs @@ -20172,6 +20172,27 @@ var require_util8 = __commonJS({ } var kEnumerableProperty = /* @__PURE__ */ Object.create(null); kEnumerableProperty.enumerable = true; + var normalizedMethodRecordsBase = { + delete: "DELETE", + DELETE: "DELETE", + get: "GET", + GET: "GET", + head: "HEAD", + HEAD: "HEAD", + options: "OPTIONS", + OPTIONS: "OPTIONS", + post: "POST", + POST: "POST", + put: "PUT", + PUT: "PUT" + }; + var normalizedMethodRecords = { + ...normalizedMethodRecordsBase, + patch: "patch", + PATCH: "PATCH" + }; + Object.setPrototypeOf(normalizedMethodRecordsBase, null); + Object.setPrototypeOf(normalizedMethodRecords, null); module2.exports = { kEnumerableProperty, nop, @@ -20210,6 +20231,8 @@ var require_util8 = __commonJS({ isValidHeaderValue, isTokenCharCode, parseRangeHeader, + normalizedMethodRecordsBase, + normalizedMethodRecords, isValidPort, isHttpOrHttpsPrefixed, nodeMajor, @@ -20425,7 +20448,8 @@ var require_request3 = __commonJS({ isBlobLike, buildURL, validateHandler, - getServerName + getServerName, + normalizedMethodRecords } = require_util8(); var { channels } = require_diagnostics(); var { headerNameLowerCasedRecord } = require_constants6(); @@ -20452,12 +20476,12 @@ var require_request3 = __commonJS({ throw new InvalidArgumentError("path must be a string"); } else if (path[0] !== "/" && !(path.startsWith("http://") || path.startsWith("https://")) && method !== "CONNECT") { throw new InvalidArgumentError("path must be an absolute URL or start with a slash"); - } else if (invalidPathRegex.exec(path) !== null) { + } else if (invalidPathRegex.test(path)) { throw new InvalidArgumentError("invalid request path"); } if (typeof method !== "string") { throw new InvalidArgumentError("method must be a string"); - } else if (!isValidHTTPToken(method)) { + } else if (normalizedMethodRecords[method] === void 0 && !isValidHTTPToken(method)) { throw new InvalidArgumentError("invalid request method"); } if (upgrade && typeof upgrade !== "string") { @@ -21002,7 +21026,7 @@ var require_connect2 = __commonJS({ } }; } - function buildConnector({ allowH2, maxCachedSessions, socketPath, timeout, ...opts }) { + function buildConnector({ allowH2, maxCachedSessions, socketPath, timeout, session: customSession, ...opts }) { if (maxCachedSessions != null && (!Number.isInteger(maxCachedSessions) || maxCachedSessions < 0)) { throw new InvalidArgumentError("maxCachedSessions must be a positive integer or zero"); } @@ -21018,7 +21042,7 @@ var require_connect2 = __commonJS({ } servername = servername || options.servername || util.getServerName(host) || null; const sessionKey = servername || hostname; - const session = sessionCache.get(sessionKey) || null; + const session = customSession || sessionCache.get(sessionKey) || null; assert(sessionKey); socket = tls.connect({ highWaterMark: 16384, @@ -22536,7 +22560,7 @@ var require_util9 = __commonJS({ var { getGlobalOrigin } = require_global3(); var { collectASequenceOfCodePoints, collectAnHTTPQuotedString, removeChars, parseMIMEType } = require_data_url(); var { performance: performance2 } = require("node:perf_hooks"); - var { isBlobLike, ReadableStreamFrom, isValidHTTPToken } = require_util8(); + var { isBlobLike, ReadableStreamFrom, isValidHTTPToken, normalizedMethodRecordsBase } = require_util8(); var assert = require("node:assert"); var { isUint8Array } = require("node:util/types"); var { webidl } = require_webidl2(); @@ -22643,7 +22667,7 @@ var require_util9 = __commonJS({ } function appendRequestOriginHeader(request2) { let serializedOrigin = request2.origin; - if (serializedOrigin === "client") { + if (serializedOrigin === "client" || serializedOrigin === void 0) { return; } if (request2.responseTainting === "cors" || request2.mode === "websocket") { @@ -22924,29 +22948,8 @@ var require_util9 = __commonJS({ function isCancelled(fetchParams) { return fetchParams.controller.state === "aborted" || fetchParams.controller.state === "terminated"; } - var normalizeMethodRecordBase = { - delete: "DELETE", - DELETE: "DELETE", - get: "GET", - GET: "GET", - head: "HEAD", - HEAD: "HEAD", - options: "OPTIONS", - OPTIONS: "OPTIONS", - post: "POST", - POST: "POST", - put: "PUT", - PUT: "PUT" - }; - var normalizeMethodRecord = { - ...normalizeMethodRecordBase, - patch: "patch", - PATCH: "PATCH" - }; - Object.setPrototypeOf(normalizeMethodRecordBase, null); - Object.setPrototypeOf(normalizeMethodRecord, null); function normalizeMethod(method) { - return normalizeMethodRecordBase[method.toLowerCase()] ?? method; + return normalizedMethodRecordsBase[method.toLowerCase()] ?? method; } function serializeJavascriptValueToJSONString(value) { const result = JSON.stringify(value); @@ -23083,7 +23086,7 @@ var require_util9 = __commonJS({ } }); } - async function fullyReadBody(body, processBody, processBodyError, shouldClone) { + async function fullyReadBody(body, processBody, processBodyError) { const successSteps = processBody; const errorSteps = processBodyError; let reader; @@ -23094,7 +23097,7 @@ var require_util9 = __commonJS({ return; } try { - successSteps(await readAllBytes(reader, shouldClone)); + successSteps(await readAllBytes(reader)); } catch (e) { errorSteps(e); } @@ -23117,19 +23120,12 @@ var require_util9 = __commonJS({ assert(!invalidIsomorphicEncodeValueRegex.test(input)); return input; } - async function readAllBytes(reader, shouldClone) { + async function readAllBytes(reader) { const bytes = []; let byteLength = 0; while (true) { const { done, value: chunk } = await reader.read(); if (done) { - if (bytes.length === 1) { - const { buffer, byteOffset, byteLength: byteLength2 } = bytes[0]; - if (shouldClone === false) { - return Buffer.from(buffer, byteOffset, byteLength2); - } - return Buffer.from(buffer.slice(byteOffset, byteOffset + byteLength2), 0, byteLength2); - } return Buffer.concat(bytes, byteLength); } if (!isUint8Array(chunk)) { @@ -23392,7 +23388,6 @@ var require_util9 = __commonJS({ urlHasHttpsScheme, urlIsHttpHttpsScheme, readAllBytes, - normalizeMethodRecord, simpleRangeHeaderValue, buildContentRange, parseMetadata, @@ -24064,18 +24059,18 @@ Content-Type: ${value.type || "application/octet-stream"}\r mimeType = serializeAMimeType(mimeType); } return new Blob2([bytes], { type: mimeType }); - }, instance, false); + }, instance); }, arrayBuffer() { return consumeBody(this, (bytes) => { - return bytes.buffer; - }, instance, true); + return new Uint8Array(bytes).buffer; + }, instance); }, text() { - return consumeBody(this, utf8DecodeBytes, instance, false); + return consumeBody(this, utf8DecodeBytes, instance); }, json() { - return consumeBody(this, parseJSONFromBytes, instance, false); + return consumeBody(this, parseJSONFromBytes, instance); }, formData() { return consumeBody(this, (value) => { @@ -24104,12 +24099,12 @@ Content-Type: ${value.type || "application/octet-stream"}\r throw new TypeError( 'Content-Type was not one of "multipart/form-data" or "application/x-www-form-urlencoded".' ); - }, instance, false); + }, instance); }, bytes() { return consumeBody(this, (bytes) => { - return new Uint8Array(bytes.buffer, 0, bytes.byteLength); - }, instance, true); + return new Uint8Array(bytes); + }, instance); } }; return methods; @@ -24117,7 +24112,7 @@ Content-Type: ${value.type || "application/octet-stream"}\r function mixinBody(prototype) { Object.assign(prototype.prototype, bodyMixinMethods(prototype)); } - async function consumeBody(object, convertBytesToJSValue, instance, shouldClone) { + async function consumeBody(object, convertBytesToJSValue, instance) { webidl.brandCheck(object, instance); if (bodyUnusable(object[kState].body)) { throw new TypeError("Body is unusable: Body has already been read"); @@ -24136,7 +24131,7 @@ Content-Type: ${value.type || "application/octet-stream"}\r successSteps(Buffer.allocUnsafe(0)); return promise.promise; } - await fullyReadBody(object[kState].body, successSteps, errorSteps, shouldClone); + await fullyReadBody(object[kState].body, successSteps, errorSteps); return promise.promise; } function bodyUnusable(body) { @@ -24889,25 +24884,25 @@ upgrade: ${upgrade}\r channels.sendHeaders.publish({ request: request2, headers: header, socket }); } if (!body || bodyLength === 0) { - writeBuffer({ abort, body: null, client, request: request2, socket, contentLength, header, expectsPayload }); + writeBuffer(abort, null, client, request2, socket, contentLength, header, expectsPayload); } else if (util.isBuffer(body)) { - writeBuffer({ abort, body, client, request: request2, socket, contentLength, header, expectsPayload }); + writeBuffer(abort, body, client, request2, socket, contentLength, header, expectsPayload); } else if (util.isBlobLike(body)) { if (typeof body.stream === "function") { - writeIterable({ abort, body: body.stream(), client, request: request2, socket, contentLength, header, expectsPayload }); + writeIterable(abort, body.stream(), client, request2, socket, contentLength, header, expectsPayload); } else { - writeBlob({ abort, body, client, request: request2, socket, contentLength, header, expectsPayload }); + writeBlob(abort, body, client, request2, socket, contentLength, header, expectsPayload); } } else if (util.isStream(body)) { - writeStream({ abort, body, client, request: request2, socket, contentLength, header, expectsPayload }); + writeStream(abort, body, client, request2, socket, contentLength, header, expectsPayload); } else if (util.isIterable(body)) { - writeIterable({ abort, body, client, request: request2, socket, contentLength, header, expectsPayload }); + writeIterable(abort, body, client, request2, socket, contentLength, header, expectsPayload); } else { assert(false); } return true; } - function writeStream({ abort, body, client, request: request2, socket, contentLength, header, expectsPayload }) { + function writeStream(abort, body, client, request2, socket, contentLength, header, expectsPayload) { assert(contentLength !== 0 || client[kRunning] === 0, "stream body cannot be pipelined"); let finished = false; const writer = new AsyncWriter({ abort, socket, request: request2, contentLength, client, expectsPayload, header }); @@ -24976,7 +24971,7 @@ upgrade: ${upgrade}\r setImmediate(onClose); } } - function writeBuffer({ abort, body, client, request: request2, socket, contentLength, header, expectsPayload }) { + function writeBuffer(abort, body, client, request2, socket, contentLength, header, expectsPayload) { try { if (!body) { if (contentLength === 0) { @@ -25007,7 +25002,7 @@ upgrade: ${upgrade}\r abort(err); } } - async function writeBlob({ abort, body, client, request: request2, socket, contentLength, header, expectsPayload }) { + async function writeBlob(abort, body, client, request2, socket, contentLength, header, expectsPayload) { assert(contentLength === body.size, "blob body must have content length"); try { if (contentLength != null && contentLength !== body.size) { @@ -25030,7 +25025,7 @@ upgrade: ${upgrade}\r abort(err); } } - async function writeIterable({ abort, body, client, request: request2, socket, contentLength, header, expectsPayload }) { + async function writeIterable(abort, body, client, request2, socket, contentLength, header, expectsPayload) { assert(contentLength !== 0 || client[kRunning] === 0, "iterator body cannot be pipelined"); let callback = null; function onDrain() { @@ -25493,81 +25488,79 @@ var require_client_h2 = __commonJS({ return true; function writeBodyH2() { if (!body || contentLength === 0) { - writeBuffer({ + writeBuffer( abort, + stream, + null, client, - request: request2, + request2, + client[kSocket], contentLength, - expectsPayload, - h2stream: stream, - body: null, - socket: client[kSocket] - }); + expectsPayload + ); } else if (util.isBuffer(body)) { - writeBuffer({ + writeBuffer( abort, + stream, + body, client, - request: request2, + request2, + client[kSocket], contentLength, - body, - expectsPayload, - h2stream: stream, - socket: client[kSocket] - }); + expectsPayload + ); } else if (util.isBlobLike(body)) { if (typeof body.stream === "function") { - writeIterable({ + writeIterable( abort, + stream, + body.stream(), client, - request: request2, + request2, + client[kSocket], contentLength, - expectsPayload, - h2stream: stream, - body: body.stream(), - socket: client[kSocket] - }); + expectsPayload + ); } else { - writeBlob({ + writeBlob( abort, + stream, body, client, - request: request2, + request2, + client[kSocket], contentLength, - expectsPayload, - h2stream: stream, - socket: client[kSocket] - }); + expectsPayload + ); } } else if (util.isStream(body)) { - writeStream({ + writeStream( abort, + client[kSocket], + expectsPayload, + stream, body, client, - request: request2, - contentLength, - expectsPayload, - socket: client[kSocket], - h2stream: stream, - header: "" - }); + request2, + contentLength + ); } else if (util.isIterable(body)) { - writeIterable({ + writeIterable( abort, + stream, body, client, - request: request2, + request2, + client[kSocket], contentLength, - expectsPayload, - header: "", - h2stream: stream, - socket: client[kSocket] - }); + expectsPayload + ); } else { assert(false); } } } - function writeBuffer({ abort, h2stream, body, client, request: request2, socket, contentLength, expectsPayload }) { + function writeBuffer(abort, h2stream, body, client, request2, socket, contentLength, expectsPayload) { try { if (body != null && util.isBuffer(body)) { assert(contentLength === body.byteLength, "buffer body must have content length"); @@ -25586,7 +25579,7 @@ var require_client_h2 = __commonJS({ abort(error); } } - function writeStream({ abort, socket, expectsPayload, h2stream, body, client, request: request2, contentLength }) { + function writeStream(abort, socket, expectsPayload, h2stream, body, client, request2, contentLength) { assert(contentLength !== 0 || client[kRunning] === 0, "stream body cannot be pipelined"); const pipe = pipeline( body, @@ -25610,7 +25603,7 @@ var require_client_h2 = __commonJS({ request2.onBodySent(chunk); } } - async function writeBlob({ abort, h2stream, body, client, request: request2, socket, contentLength, expectsPayload }) { + async function writeBlob(abort, h2stream, body, client, request2, socket, contentLength, expectsPayload) { assert(contentLength === body.size, "blob body must have content length"); try { if (contentLength != null && contentLength !== body.size) { @@ -25631,7 +25624,7 @@ var require_client_h2 = __commonJS({ abort(err); } } - async function writeIterable({ abort, h2stream, body, client, request: request2, socket, contentLength, expectsPayload }) { + async function writeIterable(abort, h2stream, body, client, request2, socket, contentLength, expectsPayload) { assert(contentLength !== 0 || client[kRunning] === 0, "iterator body cannot be pipelined"); let callback = null; function onDrain() { @@ -27376,7 +27369,7 @@ var require_retry_handler = __commonJS({ this.abort( new RequestRetryError("Content-Range mismatch", statusCode, { headers, - count: this.retryCount + data: { count: this.retryCount } }) ); return false; @@ -27385,7 +27378,7 @@ var require_retry_handler = __commonJS({ this.abort( new RequestRetryError("ETag mismatch", statusCode, { headers, - count: this.retryCount + data: { count: this.retryCount } }) ); return false; @@ -30609,9 +30602,7 @@ var require_request4 = __commonJS({ var { isValidHTTPToken, sameOrigin, - normalizeMethod, - environmentSettingsObject, - normalizeMethodRecord + environmentSettingsObject } = require_util9(); var { forbiddenMethodsSet, @@ -30623,7 +30614,7 @@ var require_request4 = __commonJS({ requestCache, requestDuplex } = require_constants8(); - var { kEnumerableProperty } = util; + var { kEnumerableProperty, normalizedMethodRecordsBase, normalizedMethodRecords } = util; var { kHeaders, kSignal, kState, kDispatcher } = require_symbols7(); var { webidl } = require_webidl2(); var { URLSerializer } = require_data_url(); @@ -30820,17 +30811,18 @@ var require_request4 = __commonJS({ } if (init.method !== void 0) { let method = init.method; - const mayBeNormalized = normalizeMethodRecord[method]; + const mayBeNormalized = normalizedMethodRecords[method]; if (mayBeNormalized !== void 0) { request2.method = mayBeNormalized; } else { if (!isValidHTTPToken(method)) { throw new TypeError(`'${method}' is not a valid HTTP method.`); } - if (forbiddenMethodsSet.has(method.toUpperCase())) { + const upperCase = method.toUpperCase(); + if (forbiddenMethodsSet.has(upperCase)) { throw new TypeError(`'${method}' HTTP method is unsupported.`); } - method = normalizeMethod(method); + method = normalizedMethodRecordsBase[upperCase] ?? method; request2.method = method; } if (!patchMethodWarning && request2.method === "patch") { @@ -35546,7 +35538,6 @@ var require_websocket2 = __commonJS({ var { types } = require("node:util"); var { ErrorEvent, CloseEvent } = require_events2(); var { SendQueue } = require_sender(); - var experimentalWarned = false; var WebSocket = class _WebSocket extends EventTarget { #events = { open: null, @@ -35567,12 +35558,6 @@ var require_websocket2 = __commonJS({ super(); const prefix = "WebSocket constructor"; webidl.argumentLengthCheck(arguments, 1, prefix); - if (!experimentalWarned) { - experimentalWarned = true; - process.emitWarning("WebSockets are experimental, expect them to change at any time.", { - code: "UNDICI-WS" - }); - } const options = webidl.converters["DOMString or sequence or WebSocketInit"](protocols, prefix, "options"); url = webidl.converters.USVString(url, prefix, "url"); protocols = options.protocols; @@ -39221,19 +39206,19 @@ async function get(cache, options) { permissionsString, singleFileName ] = result.split("|"); - const permissions = options.permissions || permissionsString.split(/,/).reduce((permissions2, string) => { + const permissions2 = options.permissions || permissionsString.split(/,/).reduce((permissions22, string) => { if (/!$/.test(string)) { - permissions2[string.slice(0, -1)] = "write"; + permissions22[string.slice(0, -1)] = "write"; } else { - permissions2[string] = "read"; + permissions22[string] = "read"; } - return permissions2; + return permissions22; }, {}); return { token, createdAt, expiresAt, - permissions, + permissions: permissions2, repositoryIds: options.repositoryIds, repositoryNames: options.repositoryNames, singleFileName, @@ -39257,11 +39242,11 @@ async function set(cache, options, data) { } function optionsToCacheKey({ installationId, - permissions = {}, + permissions: permissions2 = {}, repositoryIds = [], repositoryNames = [] }) { - const permissionsString = Object.keys(permissions).sort().map((name) => permissions[name] === "read" ? name : `${name}!`).join(","); + const permissionsString = Object.keys(permissions2).sort().map((name) => permissions2[name] === "read" ? name : `${name}!`).join(","); const repositoryIdsString = repositoryIds.sort().join(","); const repositoryNamesString = repositoryNames.join(","); return [ @@ -39277,7 +39262,7 @@ function toTokenAuthentication({ createdAt, expiresAt, repositorySelection, - permissions, + permissions: permissions2, repositoryIds, repositoryNames, singleFileName @@ -39288,7 +39273,7 @@ function toTokenAuthentication({ tokenType: "installation", token, installationId, - permissions, + permissions: permissions2, createdAt, expiresAt, repositorySelection @@ -39326,7 +39311,7 @@ async function getInstallationAuthentication(state, options, customRequest) { token: token2, createdAt: createdAt2, expiresAt: expiresAt2, - permissions: permissions2, + permissions: permissions22, repositoryIds: repositoryIds2, repositoryNames: repositoryNames2, singleFileName: singleFileName2, @@ -39337,7 +39322,7 @@ async function getInstallationAuthentication(state, options, customRequest) { token: token2, createdAt: createdAt2, expiresAt: expiresAt2, - permissions: permissions2, + permissions: permissions22, repositorySelection: repositorySelection2, repositoryIds: repositoryIds2, repositoryNames: repositoryNames2, @@ -39368,7 +39353,7 @@ async function getInstallationAuthentication(state, options, customRequest) { authorization: `bearer ${appAuthentication.token}` } }); - const permissions = permissionsOptional || {}; + const permissions2 = permissionsOptional || {}; const repositorySelection = repositorySelectionOptional || "all"; const repositoryIds = repositories2 ? repositories2.map((r) => r.id) : void 0; const repositoryNames = repositories2 ? repositories2.map((repo) => repo.name) : void 0; @@ -39378,7 +39363,7 @@ async function getInstallationAuthentication(state, options, customRequest) { createdAt, expiresAt, repositorySelection, - permissions, + permissions: permissions2, repositoryIds, repositoryNames, singleFileName @@ -39389,7 +39374,7 @@ async function getInstallationAuthentication(state, options, customRequest) { createdAt, expiresAt, repositorySelection, - permissions, + permissions: permissions2, repositoryIds, repositoryNames, singleFileName @@ -39691,9 +39676,10 @@ async function pRetry(input, options) { } // lib/main.js -async function main(appId2, privateKey2, owner2, repositories2, core3, createAppAuth2, request2, skipTokenRevoke2) { +async function main(appId2, privateKey2, owner2, repositories2, core3, createAppAuth2, request2, skipTokenRevoke2, permissions2) { let parsedOwner = ""; let parsedRepositoryNames = ""; + let parsedPermissions = {}; if (!owner2 && !repositories2) { [parsedOwner, parsedRepositoryNames] = String( process.env.GITHUB_REPOSITORY @@ -39722,6 +39708,14 @@ async function main(appId2, privateKey2, owner2, repositories2, core3, createApp `owner and repositories set, creating token for repositories "${repositories2}" owned by "${owner2}"` ); } + if (permissions2) { + const sep = permissions2.includes(",") ? "," : "\n"; + const permissionSpecs = permissions2.split(sep); + for (spec of permissionSpecs) { + const [key, value] = spec.split(":"); + parsedPermissions[key.trim()] = value.trim(); + } + } const auth5 = createAppAuth2({ appId: appId2, privateKey: privateKey2, @@ -39729,7 +39723,7 @@ async function main(appId2, privateKey2, owner2, repositories2, core3, createApp }); let authentication, installationId, appSlug; if (parsedRepositoryNames) { - ({ authentication, installationId, appSlug } = await pRetry(() => getTokenFromRepository(request2, auth5, parsedOwner, parsedRepositoryNames), { + ({ authentication, installationId, appSlug } = await pRetry(() => getTokenFromRepository(request2, auth5, parsedOwner, parsedRepositoryNames, parsedPermissions), { onFailedAttempt: (error) => { core3.info( `Failed to create token for "${parsedRepositoryNames}" (attempt ${error.attemptNumber}): ${error.message}` @@ -39738,7 +39732,7 @@ async function main(appId2, privateKey2, owner2, repositories2, core3, createApp retries: 3 })); } else { - ({ authentication, installationId, appSlug } = await pRetry(() => getTokenFromOwner(request2, auth5, parsedOwner), { + ({ authentication, installationId, appSlug } = await pRetry(() => getTokenFromOwner(request2, auth5, parsedOwner, parsedPermissions), { onFailedAttempt: (error) => { core3.info( `Failed to create token for "${parsedOwner}" (attempt ${error.attemptNumber}): ${error.message}` @@ -39756,7 +39750,7 @@ async function main(appId2, privateKey2, owner2, repositories2, core3, createApp core3.saveState("expiresAt", authentication.expiresAt); } } -async function getTokenFromOwner(request2, auth5, parsedOwner) { +async function getTokenFromOwner(request2, auth5, parsedOwner, parsedPermissions) { const response = await request2("GET /orgs/{org}/installation", { org: parsedOwner, request: { @@ -39771,15 +39765,19 @@ async function getTokenFromOwner(request2, auth5, parsedOwner) { } }); }); - const authentication = await auth5({ + const authConfig = { type: "installation", installationId: response.data.id - }); + }; + if (Object.keys(parsedPermissions).length > 0) { + authConfig["permissions"] = parsedPermissions; + } + const authentication = await auth5(authConfig); const installationId = response.data.id; const appSlug = response.data["app_slug"]; return { authentication, installationId, appSlug }; } -async function getTokenFromRepository(request2, auth5, parsedOwner, parsedRepositoryNames) { +async function getTokenFromRepository(request2, auth5, parsedOwner, parsedRepositoryNames, parsedPermissions) { const response = await request2("GET /repos/{owner}/{repo}/installation", { owner: parsedOwner, repo: parsedRepositoryNames.split(",")[0], @@ -39787,11 +39785,15 @@ async function getTokenFromRepository(request2, auth5, parsedOwner, parsedReposi hook: auth5.hook } }); - const authentication = await auth5({ + const authConfig = { type: "installation", installationId: response.data.id, repositoryNames: parsedRepositoryNames.split(",") - }); + }; + if (Object.keys(parsedPermissions).length > 0) { + authConfig["permissions"] = parsedPermissions; + } + const authentication = await auth5(authConfig); const installationId = response.data.id; const appSlug = response.data["app_slug"]; return { authentication, installationId, appSlug }; @@ -39844,6 +39846,7 @@ var repositories = import_core2.default.getInput("repositories"); var skipTokenRevoke = Boolean( import_core2.default.getInput("skip-token-revoke") || import_core2.default.getInput("skip_token_revoke") ); +var permissions = import_core2.default.getInput("permissions"); main( appId, privateKey, @@ -39852,7 +39855,8 @@ main( import_core2.default, createAppAuth, request_default, - skipTokenRevoke + skipTokenRevoke, + permissions ).catch((error) => { console.error(error); import_core2.default.setFailed(error.message); diff --git a/dist/post.cjs b/dist/post.cjs index 0307466..090c0f1 100644 --- a/dist/post.cjs +++ b/dist/post.cjs @@ -19943,6 +19943,27 @@ var require_util8 = __commonJS({ } var kEnumerableProperty = /* @__PURE__ */ Object.create(null); kEnumerableProperty.enumerable = true; + var normalizedMethodRecordsBase = { + delete: "DELETE", + DELETE: "DELETE", + get: "GET", + GET: "GET", + head: "HEAD", + HEAD: "HEAD", + options: "OPTIONS", + OPTIONS: "OPTIONS", + post: "POST", + POST: "POST", + put: "PUT", + PUT: "PUT" + }; + var normalizedMethodRecords = { + ...normalizedMethodRecordsBase, + patch: "patch", + PATCH: "PATCH" + }; + Object.setPrototypeOf(normalizedMethodRecordsBase, null); + Object.setPrototypeOf(normalizedMethodRecords, null); module2.exports = { kEnumerableProperty, nop, @@ -19981,6 +20002,8 @@ var require_util8 = __commonJS({ isValidHeaderValue, isTokenCharCode, parseRangeHeader, + normalizedMethodRecordsBase, + normalizedMethodRecords, isValidPort, isHttpOrHttpsPrefixed, nodeMajor, @@ -20196,7 +20219,8 @@ var require_request3 = __commonJS({ isBlobLike, buildURL, validateHandler, - getServerName + getServerName, + normalizedMethodRecords } = require_util8(); var { channels } = require_diagnostics(); var { headerNameLowerCasedRecord } = require_constants6(); @@ -20223,12 +20247,12 @@ var require_request3 = __commonJS({ throw new InvalidArgumentError("path must be a string"); } else if (path[0] !== "/" && !(path.startsWith("http://") || path.startsWith("https://")) && method !== "CONNECT") { throw new InvalidArgumentError("path must be an absolute URL or start with a slash"); - } else if (invalidPathRegex.exec(path) !== null) { + } else if (invalidPathRegex.test(path)) { throw new InvalidArgumentError("invalid request path"); } if (typeof method !== "string") { throw new InvalidArgumentError("method must be a string"); - } else if (!isValidHTTPToken(method)) { + } else if (normalizedMethodRecords[method] === void 0 && !isValidHTTPToken(method)) { throw new InvalidArgumentError("invalid request method"); } if (upgrade && typeof upgrade !== "string") { @@ -20773,7 +20797,7 @@ var require_connect2 = __commonJS({ } }; } - function buildConnector({ allowH2, maxCachedSessions, socketPath, timeout, ...opts }) { + function buildConnector({ allowH2, maxCachedSessions, socketPath, timeout, session: customSession, ...opts }) { if (maxCachedSessions != null && (!Number.isInteger(maxCachedSessions) || maxCachedSessions < 0)) { throw new InvalidArgumentError("maxCachedSessions must be a positive integer or zero"); } @@ -20789,7 +20813,7 @@ var require_connect2 = __commonJS({ } servername = servername || options.servername || util.getServerName(host) || null; const sessionKey = servername || hostname; - const session = sessionCache.get(sessionKey) || null; + const session = customSession || sessionCache.get(sessionKey) || null; assert(sessionKey); socket = tls.connect({ highWaterMark: 16384, @@ -22307,7 +22331,7 @@ var require_util9 = __commonJS({ var { getGlobalOrigin } = require_global3(); var { collectASequenceOfCodePoints, collectAnHTTPQuotedString, removeChars, parseMIMEType } = require_data_url(); var { performance: performance2 } = require("node:perf_hooks"); - var { isBlobLike, ReadableStreamFrom, isValidHTTPToken } = require_util8(); + var { isBlobLike, ReadableStreamFrom, isValidHTTPToken, normalizedMethodRecordsBase } = require_util8(); var assert = require("node:assert"); var { isUint8Array } = require("node:util/types"); var { webidl } = require_webidl2(); @@ -22414,7 +22438,7 @@ var require_util9 = __commonJS({ } function appendRequestOriginHeader(request2) { let serializedOrigin = request2.origin; - if (serializedOrigin === "client") { + if (serializedOrigin === "client" || serializedOrigin === void 0) { return; } if (request2.responseTainting === "cors" || request2.mode === "websocket") { @@ -22695,29 +22719,8 @@ var require_util9 = __commonJS({ function isCancelled(fetchParams) { return fetchParams.controller.state === "aborted" || fetchParams.controller.state === "terminated"; } - var normalizeMethodRecordBase = { - delete: "DELETE", - DELETE: "DELETE", - get: "GET", - GET: "GET", - head: "HEAD", - HEAD: "HEAD", - options: "OPTIONS", - OPTIONS: "OPTIONS", - post: "POST", - POST: "POST", - put: "PUT", - PUT: "PUT" - }; - var normalizeMethodRecord = { - ...normalizeMethodRecordBase, - patch: "patch", - PATCH: "PATCH" - }; - Object.setPrototypeOf(normalizeMethodRecordBase, null); - Object.setPrototypeOf(normalizeMethodRecord, null); function normalizeMethod(method) { - return normalizeMethodRecordBase[method.toLowerCase()] ?? method; + return normalizedMethodRecordsBase[method.toLowerCase()] ?? method; } function serializeJavascriptValueToJSONString(value) { const result = JSON.stringify(value); @@ -22854,7 +22857,7 @@ var require_util9 = __commonJS({ } }); } - async function fullyReadBody(body, processBody, processBodyError, shouldClone) { + async function fullyReadBody(body, processBody, processBodyError) { const successSteps = processBody; const errorSteps = processBodyError; let reader; @@ -22865,7 +22868,7 @@ var require_util9 = __commonJS({ return; } try { - successSteps(await readAllBytes(reader, shouldClone)); + successSteps(await readAllBytes(reader)); } catch (e) { errorSteps(e); } @@ -22888,19 +22891,12 @@ var require_util9 = __commonJS({ assert(!invalidIsomorphicEncodeValueRegex.test(input)); return input; } - async function readAllBytes(reader, shouldClone) { + async function readAllBytes(reader) { const bytes = []; let byteLength = 0; while (true) { const { done, value: chunk } = await reader.read(); if (done) { - if (bytes.length === 1) { - const { buffer, byteOffset, byteLength: byteLength2 } = bytes[0]; - if (shouldClone === false) { - return Buffer.from(buffer, byteOffset, byteLength2); - } - return Buffer.from(buffer.slice(byteOffset, byteOffset + byteLength2), 0, byteLength2); - } return Buffer.concat(bytes, byteLength); } if (!isUint8Array(chunk)) { @@ -23163,7 +23159,6 @@ var require_util9 = __commonJS({ urlHasHttpsScheme, urlIsHttpHttpsScheme, readAllBytes, - normalizeMethodRecord, simpleRangeHeaderValue, buildContentRange, parseMetadata, @@ -23835,18 +23830,18 @@ Content-Type: ${value.type || "application/octet-stream"}\r mimeType = serializeAMimeType(mimeType); } return new Blob2([bytes], { type: mimeType }); - }, instance, false); + }, instance); }, arrayBuffer() { return consumeBody(this, (bytes) => { - return bytes.buffer; - }, instance, true); + return new Uint8Array(bytes).buffer; + }, instance); }, text() { - return consumeBody(this, utf8DecodeBytes, instance, false); + return consumeBody(this, utf8DecodeBytes, instance); }, json() { - return consumeBody(this, parseJSONFromBytes, instance, false); + return consumeBody(this, parseJSONFromBytes, instance); }, formData() { return consumeBody(this, (value) => { @@ -23875,12 +23870,12 @@ Content-Type: ${value.type || "application/octet-stream"}\r throw new TypeError( 'Content-Type was not one of "multipart/form-data" or "application/x-www-form-urlencoded".' ); - }, instance, false); + }, instance); }, bytes() { return consumeBody(this, (bytes) => { - return new Uint8Array(bytes.buffer, 0, bytes.byteLength); - }, instance, true); + return new Uint8Array(bytes); + }, instance); } }; return methods; @@ -23888,7 +23883,7 @@ Content-Type: ${value.type || "application/octet-stream"}\r function mixinBody(prototype) { Object.assign(prototype.prototype, bodyMixinMethods(prototype)); } - async function consumeBody(object, convertBytesToJSValue, instance, shouldClone) { + async function consumeBody(object, convertBytesToJSValue, instance) { webidl.brandCheck(object, instance); if (bodyUnusable(object[kState].body)) { throw new TypeError("Body is unusable: Body has already been read"); @@ -23907,7 +23902,7 @@ Content-Type: ${value.type || "application/octet-stream"}\r successSteps(Buffer.allocUnsafe(0)); return promise.promise; } - await fullyReadBody(object[kState].body, successSteps, errorSteps, shouldClone); + await fullyReadBody(object[kState].body, successSteps, errorSteps); return promise.promise; } function bodyUnusable(body) { @@ -24660,25 +24655,25 @@ upgrade: ${upgrade}\r channels.sendHeaders.publish({ request: request2, headers: header, socket }); } if (!body || bodyLength === 0) { - writeBuffer({ abort, body: null, client, request: request2, socket, contentLength, header, expectsPayload }); + writeBuffer(abort, null, client, request2, socket, contentLength, header, expectsPayload); } else if (util.isBuffer(body)) { - writeBuffer({ abort, body, client, request: request2, socket, contentLength, header, expectsPayload }); + writeBuffer(abort, body, client, request2, socket, contentLength, header, expectsPayload); } else if (util.isBlobLike(body)) { if (typeof body.stream === "function") { - writeIterable({ abort, body: body.stream(), client, request: request2, socket, contentLength, header, expectsPayload }); + writeIterable(abort, body.stream(), client, request2, socket, contentLength, header, expectsPayload); } else { - writeBlob({ abort, body, client, request: request2, socket, contentLength, header, expectsPayload }); + writeBlob(abort, body, client, request2, socket, contentLength, header, expectsPayload); } } else if (util.isStream(body)) { - writeStream({ abort, body, client, request: request2, socket, contentLength, header, expectsPayload }); + writeStream(abort, body, client, request2, socket, contentLength, header, expectsPayload); } else if (util.isIterable(body)) { - writeIterable({ abort, body, client, request: request2, socket, contentLength, header, expectsPayload }); + writeIterable(abort, body, client, request2, socket, contentLength, header, expectsPayload); } else { assert(false); } return true; } - function writeStream({ abort, body, client, request: request2, socket, contentLength, header, expectsPayload }) { + function writeStream(abort, body, client, request2, socket, contentLength, header, expectsPayload) { assert(contentLength !== 0 || client[kRunning] === 0, "stream body cannot be pipelined"); let finished = false; const writer = new AsyncWriter({ abort, socket, request: request2, contentLength, client, expectsPayload, header }); @@ -24747,7 +24742,7 @@ upgrade: ${upgrade}\r setImmediate(onClose); } } - function writeBuffer({ abort, body, client, request: request2, socket, contentLength, header, expectsPayload }) { + function writeBuffer(abort, body, client, request2, socket, contentLength, header, expectsPayload) { try { if (!body) { if (contentLength === 0) { @@ -24778,7 +24773,7 @@ upgrade: ${upgrade}\r abort(err); } } - async function writeBlob({ abort, body, client, request: request2, socket, contentLength, header, expectsPayload }) { + async function writeBlob(abort, body, client, request2, socket, contentLength, header, expectsPayload) { assert(contentLength === body.size, "blob body must have content length"); try { if (contentLength != null && contentLength !== body.size) { @@ -24801,7 +24796,7 @@ upgrade: ${upgrade}\r abort(err); } } - async function writeIterable({ abort, body, client, request: request2, socket, contentLength, header, expectsPayload }) { + async function writeIterable(abort, body, client, request2, socket, contentLength, header, expectsPayload) { assert(contentLength !== 0 || client[kRunning] === 0, "iterator body cannot be pipelined"); let callback = null; function onDrain() { @@ -25264,81 +25259,79 @@ var require_client_h2 = __commonJS({ return true; function writeBodyH2() { if (!body || contentLength === 0) { - writeBuffer({ + writeBuffer( abort, + stream, + null, client, - request: request2, + request2, + client[kSocket], contentLength, - expectsPayload, - h2stream: stream, - body: null, - socket: client[kSocket] - }); + expectsPayload + ); } else if (util.isBuffer(body)) { - writeBuffer({ + writeBuffer( abort, + stream, + body, client, - request: request2, + request2, + client[kSocket], contentLength, - body, - expectsPayload, - h2stream: stream, - socket: client[kSocket] - }); + expectsPayload + ); } else if (util.isBlobLike(body)) { if (typeof body.stream === "function") { - writeIterable({ + writeIterable( abort, + stream, + body.stream(), client, - request: request2, + request2, + client[kSocket], contentLength, - expectsPayload, - h2stream: stream, - body: body.stream(), - socket: client[kSocket] - }); + expectsPayload + ); } else { - writeBlob({ + writeBlob( abort, + stream, body, client, - request: request2, + request2, + client[kSocket], contentLength, - expectsPayload, - h2stream: stream, - socket: client[kSocket] - }); + expectsPayload + ); } } else if (util.isStream(body)) { - writeStream({ + writeStream( abort, + client[kSocket], + expectsPayload, + stream, body, client, - request: request2, - contentLength, - expectsPayload, - socket: client[kSocket], - h2stream: stream, - header: "" - }); + request2, + contentLength + ); } else if (util.isIterable(body)) { - writeIterable({ + writeIterable( abort, + stream, body, client, - request: request2, + request2, + client[kSocket], contentLength, - expectsPayload, - header: "", - h2stream: stream, - socket: client[kSocket] - }); + expectsPayload + ); } else { assert(false); } } } - function writeBuffer({ abort, h2stream, body, client, request: request2, socket, contentLength, expectsPayload }) { + function writeBuffer(abort, h2stream, body, client, request2, socket, contentLength, expectsPayload) { try { if (body != null && util.isBuffer(body)) { assert(contentLength === body.byteLength, "buffer body must have content length"); @@ -25357,7 +25350,7 @@ var require_client_h2 = __commonJS({ abort(error); } } - function writeStream({ abort, socket, expectsPayload, h2stream, body, client, request: request2, contentLength }) { + function writeStream(abort, socket, expectsPayload, h2stream, body, client, request2, contentLength) { assert(contentLength !== 0 || client[kRunning] === 0, "stream body cannot be pipelined"); const pipe = pipeline( body, @@ -25381,7 +25374,7 @@ var require_client_h2 = __commonJS({ request2.onBodySent(chunk); } } - async function writeBlob({ abort, h2stream, body, client, request: request2, socket, contentLength, expectsPayload }) { + async function writeBlob(abort, h2stream, body, client, request2, socket, contentLength, expectsPayload) { assert(contentLength === body.size, "blob body must have content length"); try { if (contentLength != null && contentLength !== body.size) { @@ -25402,7 +25395,7 @@ var require_client_h2 = __commonJS({ abort(err); } } - async function writeIterable({ abort, h2stream, body, client, request: request2, socket, contentLength, expectsPayload }) { + async function writeIterable(abort, h2stream, body, client, request2, socket, contentLength, expectsPayload) { assert(contentLength !== 0 || client[kRunning] === 0, "iterator body cannot be pipelined"); let callback = null; function onDrain() { @@ -27147,7 +27140,7 @@ var require_retry_handler = __commonJS({ this.abort( new RequestRetryError("Content-Range mismatch", statusCode, { headers, - count: this.retryCount + data: { count: this.retryCount } }) ); return false; @@ -27156,7 +27149,7 @@ var require_retry_handler = __commonJS({ this.abort( new RequestRetryError("ETag mismatch", statusCode, { headers, - count: this.retryCount + data: { count: this.retryCount } }) ); return false; @@ -30380,9 +30373,7 @@ var require_request4 = __commonJS({ var { isValidHTTPToken, sameOrigin, - normalizeMethod, - environmentSettingsObject, - normalizeMethodRecord + environmentSettingsObject } = require_util9(); var { forbiddenMethodsSet, @@ -30394,7 +30385,7 @@ var require_request4 = __commonJS({ requestCache, requestDuplex } = require_constants8(); - var { kEnumerableProperty } = util; + var { kEnumerableProperty, normalizedMethodRecordsBase, normalizedMethodRecords } = util; var { kHeaders, kSignal, kState, kDispatcher } = require_symbols7(); var { webidl } = require_webidl2(); var { URLSerializer } = require_data_url(); @@ -30591,17 +30582,18 @@ var require_request4 = __commonJS({ } if (init.method !== void 0) { let method = init.method; - const mayBeNormalized = normalizeMethodRecord[method]; + const mayBeNormalized = normalizedMethodRecords[method]; if (mayBeNormalized !== void 0) { request2.method = mayBeNormalized; } else { if (!isValidHTTPToken(method)) { throw new TypeError(`'${method}' is not a valid HTTP method.`); } - if (forbiddenMethodsSet.has(method.toUpperCase())) { + const upperCase = method.toUpperCase(); + if (forbiddenMethodsSet.has(upperCase)) { throw new TypeError(`'${method}' HTTP method is unsupported.`); } - method = normalizeMethod(method); + method = normalizedMethodRecordsBase[upperCase] ?? method; request2.method = method; } if (!patchMethodWarning && request2.method === "patch") { @@ -35317,7 +35309,6 @@ var require_websocket2 = __commonJS({ var { types } = require("node:util"); var { ErrorEvent, CloseEvent } = require_events2(); var { SendQueue } = require_sender(); - var experimentalWarned = false; var WebSocket = class _WebSocket extends EventTarget { #events = { open: null, @@ -35338,12 +35329,6 @@ var require_websocket2 = __commonJS({ super(); const prefix = "WebSocket constructor"; webidl.argumentLengthCheck(arguments, 1, prefix); - if (!experimentalWarned) { - experimentalWarned = true; - process.emitWarning("WebSockets are experimental, expect them to change at any time.", { - code: "UNDICI-WS" - }); - } const options = webidl.converters["DOMString or sequence or WebSocketInit"](protocols, prefix, "options"); url = webidl.converters.USVString(url, prefix, "url"); protocols = options.protocols; diff --git a/lib/main.js b/lib/main.js index 97443c0..c64660c 100644 --- a/lib/main.js +++ b/lib/main.js @@ -19,10 +19,12 @@ export async function main( core, createAppAuth, request, - skipTokenRevoke + skipTokenRevoke, + permissions ) { let parsedOwner = ""; let parsedRepositoryNames = ""; + let parsedPermissions = {}; // If neither owner nor repositories are set, default to current repository if (!owner && !repositories) { @@ -64,6 +66,15 @@ export async function main( ); } + if (permissions) { + const sep = permissions.includes(",") ? "," : "\n"; + const permissionSpecs = permissions.split(sep); + for (spec of permissionSpecs) { + const [key, value] = spec.split(":") + parsedPermissions[key.trim()] = value.trim(); + } + } + const auth = createAppAuth({ appId, privateKey, @@ -74,7 +85,7 @@ export async function main( // If at least one repository is set, get installation ID from that repository if (parsedRepositoryNames) { - ({ authentication, installationId, appSlug } = await pRetry(() => getTokenFromRepository(request, auth, parsedOwner, parsedRepositoryNames), { + ({ authentication, installationId, appSlug } = await pRetry(() => getTokenFromRepository(request, auth, parsedOwner, parsedRepositoryNames, parsedPermissions), { onFailedAttempt: (error) => { core.info( `Failed to create token for "${parsedRepositoryNames}" (attempt ${error.attemptNumber}): ${error.message}` @@ -84,7 +95,7 @@ export async function main( })); } else { // Otherwise get the installation for the owner, which can either be an organization or a user account - ({ authentication, installationId, appSlug } = await pRetry(() => getTokenFromOwner(request, auth, parsedOwner), { + ({ authentication, installationId, appSlug } = await pRetry(() => getTokenFromOwner(request, auth, parsedOwner, parsedPermissions), { onFailedAttempt: (error) => { core.info( `Failed to create token for "${parsedOwner}" (attempt ${error.attemptNumber}): ${error.message}` @@ -108,7 +119,7 @@ export async function main( } } -async function getTokenFromOwner(request, auth, parsedOwner) { +async function getTokenFromOwner(request, auth, parsedOwner, parsedPermissions) { // https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#get-an-organization-installation-for-the-authenticated-app const response = await request("GET /orgs/{org}/installation", { org: parsedOwner, @@ -128,11 +139,17 @@ async function getTokenFromOwner(request, auth, parsedOwner) { }); }); - // Get token for for all repositories of the given installation - const authentication = await auth({ + const authConfig = { type: "installation", installationId: response.data.id, - }); + }; + + if (Object.keys(parsedPermissions).length > 0) { + authConfig["permissions"] = parsedPermissions; + } + + // Get token for for all repositories of the given installation + const authentication = await auth(authConfig); const installationId = response.data.id; const appSlug = response.data['app_slug']; @@ -140,7 +157,7 @@ async function getTokenFromOwner(request, auth, parsedOwner) { return { authentication, installationId, appSlug }; } -async function getTokenFromRepository(request, auth, parsedOwner, parsedRepositoryNames) { +async function getTokenFromRepository(request, auth, parsedOwner, parsedRepositoryNames, parsedPermissions) { // https://docs.github.com/rest/apps/apps?apiVersion=2022-11-28#get-a-repository-installation-for-the-authenticated-app const response = await request("GET /repos/{owner}/{repo}/installation", { owner: parsedOwner, @@ -150,15 +167,21 @@ async function getTokenFromRepository(request, auth, parsedOwner, parsedReposito }, }); - // Get token for given repositories - const authentication = await auth({ + const authConfig = { type: "installation", installationId: response.data.id, repositoryNames: parsedRepositoryNames.split(","), - }); + }; + + if (Object.keys(parsedPermissions).length > 0) { + authConfig["permissions"] = parsedPermissions; + } + + // Get token for given repositories + const authentication = await auth(authConfig); const installationId = response.data.id; const appSlug = response.data['app_slug']; return { authentication, installationId, appSlug }; - } \ No newline at end of file + } diff --git a/main.js b/main.js index b4d919d..fe22371 100644 --- a/main.js +++ b/main.js @@ -31,6 +31,8 @@ const skipTokenRevoke = Boolean( core.getInput("skip-token-revoke") || core.getInput("skip_token_revoke") ); +const permissions = core.getInput("permissions"); + main( appId, privateKey, @@ -39,7 +41,8 @@ main( core, createAppAuth, request, - skipTokenRevoke + skipTokenRevoke, + permissions ).catch((error) => { /* c8 ignore next 3 */ console.error(error); From 5aaa420d78e2ab84f27faf221b59d5a03fdbe620 Mon Sep 17 00:00:00 2001 From: Remco Vermeulen Date: Wed, 21 Aug 2024 15:18:55 -0700 Subject: [PATCH 2/3] Bump version --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index a318de7..262dd0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "create-github-app-token", - "version": "1.10.2", + "version": "1.10.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "create-github-app-token", - "version": "1.10.2", + "version": "1.10.3", "license": "MIT", "dependencies": { "@actions/core": "^1.10.1", From e4afb58b83940cbc5a5a37e05c576ed7441dce83 Mon Sep 17 00:00:00 2001 From: Remco Vermeulen Date: Fri, 13 Sep 2024 09:56:29 -0700 Subject: [PATCH 3/3] Update README.md --- README.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/README.md b/README.md index 9637ad3..261af73 100644 --- a/README.md +++ b/README.md @@ -268,6 +268,36 @@ jobs: GITHUB_TOKEN: ${{ steps.create_token.outputs.token }} ``` +### Creating a scoped token + +A GitHub App can [create](https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#create-a-scoped-access-token) a scoped token, a token with a subset of permissions assigned to the GitHub App. + +This allows you to apply the principle of least privilege to the token, but doesn't protect you against malicious code running in your workflow because that can directly access the private key used to generate the token. + +```yaml +name: Run tests on staging +on: + push: + branches: + - main + +jobs: + hello-world: + runs-on: ubuntu-latest + steps: + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.PRIVATE_KEY }} + permissions: | + metadata: read + contents: write + - uses: ./actions/staging-tests + with: + token: ${{ steps.app-token.outputs.token }} +``` + ## Inputs ### `app-id` @@ -329,6 +359,11 @@ GitHub App installation ID. GitHub App slug. +### `permissions` + +**Optional:** A comma-separate or newline-separate list of permissions for the scoped token. +See the `properties` object description in the [create-a-scoped-access-token](https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#create-a-scoped-access-token) documentation for the supported permissions. + ## How it works The action creates an installation access token using [the `POST /app/installations/{installation_id}/access_tokens` endpoint](https://docs.github.com/rest/apps/apps?apiVersion=2022-11-28#create-an-installation-access-token-for-an-app). By default,