From 343a96fcff45c50f5162eba282a87f5f574b6a8c Mon Sep 17 00:00:00 2001 From: Patrick Arlt Date: Tue, 25 Feb 2025 13:15:01 -0800 Subject: [PATCH 1/3] feat(arcgis-rest-developer-credentials): add invalidateApiKey, fix generate API key bug --- .../src/index.ts | 1 + .../src/invalidateApiKey.ts | 39 ++++++ .../src/shared/generateApiKeyToken.ts | 2 +- .../src/shared/helpers.ts | 29 ++++- .../src/shared/types/apiKeyType.ts | 20 +++ .../src/updateApiKey.ts | 42 ++++--- .../test/invalidateApiKey.test.ts | 110 ++++++++++++++++ .../test/shared/helpers.test.ts | 118 ++++++++++++------ .../test/updateApiKey.test.ts | 89 ++++++++++++- 9 files changed, 391 insertions(+), 59 deletions(-) create mode 100644 packages/arcgis-rest-developer-credentials/src/invalidateApiKey.ts create mode 100644 packages/arcgis-rest-developer-credentials/test/invalidateApiKey.test.ts diff --git a/packages/arcgis-rest-developer-credentials/src/index.ts b/packages/arcgis-rest-developer-credentials/src/index.ts index 760cfca37..929a9a0d3 100644 --- a/packages/arcgis-rest-developer-credentials/src/index.ts +++ b/packages/arcgis-rest-developer-credentials/src/index.ts @@ -4,6 +4,7 @@ export * from "./createApiKey.js"; export * from "./updateApiKey.js"; export * from "./getApiKey.js"; +export * from "./invalidateApiKey.js"; export * from "./getOAuthApp.js"; export * from "./updateOAuthApp.js"; export * from "./createOAuthApp.js"; diff --git a/packages/arcgis-rest-developer-credentials/src/invalidateApiKey.ts b/packages/arcgis-rest-developer-credentials/src/invalidateApiKey.ts new file mode 100644 index 000000000..100557af0 --- /dev/null +++ b/packages/arcgis-rest-developer-credentials/src/invalidateApiKey.ts @@ -0,0 +1,39 @@ +/* Copyright (c) 2023 Environmental Systems Research Institute, Inc. + * Apache-2.0 */ + +import { + IInvalidateApiKeyOptions, + IInvalidateApiKeyResponse +} from "./shared/types/apiKeyType.js"; +import { getRegisteredAppInfo } from "./shared/getRegisteredAppInfo.js"; +import { getPortalUrl } from "@esri/arcgis-rest-portal"; +import { request } from "@esri/arcgis-rest-request"; +import { slotForInvalidationKey } from "./shared/helpers.js"; + +/** + * Used to invalidate an API key. + */ +export async function invalidateApiKey( + requestOptions: IInvalidateApiKeyOptions +): Promise { + const portal = getPortalUrl(requestOptions); + const url = `${portal}/oauth2/revokeToken`; + + const appInfo = await getRegisteredAppInfo({ + itemId: requestOptions.itemId, + authentication: requestOptions.authentication + }); + + const params = { + client_id: appInfo.client_id, + client_secret: appInfo.client_secret, + apiToken: slotForInvalidationKey(requestOptions.apiKey), + regenerateApiToken: true, + grant_type: "client_credentials" + }; + + // authentication is not being passed to the request because client_secret acts as the auth + return request(url, { + params + }); +} diff --git a/packages/arcgis-rest-developer-credentials/src/shared/generateApiKeyToken.ts b/packages/arcgis-rest-developer-credentials/src/shared/generateApiKeyToken.ts index 9db2bf36b..33cf9c2fe 100644 --- a/packages/arcgis-rest-developer-credentials/src/shared/generateApiKeyToken.ts +++ b/packages/arcgis-rest-developer-credentials/src/shared/generateApiKeyToken.ts @@ -32,8 +32,8 @@ export async function generateApiKeyToken( grant_type: "client_credentials" }; + // authentication is not being passed to the request because client_secret acts as the auth return request(url, { - authentication: options.authentication, params }); } diff --git a/packages/arcgis-rest-developer-credentials/src/shared/helpers.ts b/packages/arcgis-rest-developer-credentials/src/shared/helpers.ts index fe15b503d..48bb313d5 100644 --- a/packages/arcgis-rest-developer-credentials/src/shared/helpers.ts +++ b/packages/arcgis-rest-developer-credentials/src/shared/helpers.ts @@ -121,10 +121,35 @@ export function filterKeys( } /** - * Used to determine if a generated key is in slot 1 or slot 2 key. + * Used to determine if a generated key is in slot 1 or slot 2 key. The full API key should be passed. `undefined` will be returned if the proper slot could not be identified. */ export function slotForKey(key: string) { - return parseInt(key.substring(key.length - 10, key.length - 9)); + const slot = parseInt(key.substring(key.length - 10, key.length - 9)); + + if (slot === 1 || slot === 2) { + return slot; + } + + return undefined; +} + +/** + * @internal + * Used to determine which slot to invalidate a key in given a number or a full or patial key. + */ +export function slotForInvalidationKey(param: string | 1 | 2) { + if (param === 1 || param === 2) { + return param; + } + + if (typeof param !== "string") { + return undefined; + } + + const fullKeySlot = slotForKey(param); + if (fullKeySlot) { + return fullKeySlot; + } } interface IGenerateApiKeyTokenOptions extends IRequestOptions { diff --git a/packages/arcgis-rest-developer-credentials/src/shared/types/apiKeyType.ts b/packages/arcgis-rest-developer-credentials/src/shared/types/apiKeyType.ts index 9de2ffbe8..9d7faf279 100644 --- a/packages/arcgis-rest-developer-credentials/src/shared/types/apiKeyType.ts +++ b/packages/arcgis-rest-developer-credentials/src/shared/types/apiKeyType.ts @@ -141,3 +141,23 @@ export interface IDeleteApiKeyResponse { itemId: string; success: boolean; } + +export interface IInvalidateApiKeyOptions + extends Omit { + /** + * {@linkcode IAuthenticationManager} authentication. + */ + authentication: IAuthenticationManager; + /** + * itemId of the item of the API key to be revoked. + */ + itemId: string; + /** + * The API key to be revoked. The full or partial API key or the slot number (1 or 2) can be provided. + */ + apiKey?: string | 1 | 2; +} + +export interface IInvalidateApiKeyResponse { + success: boolean; +} diff --git a/packages/arcgis-rest-developer-credentials/src/updateApiKey.ts b/packages/arcgis-rest-developer-credentials/src/updateApiKey.ts index ca08f3878..1501ffcd0 100644 --- a/packages/arcgis-rest-developer-credentials/src/updateApiKey.ts +++ b/packages/arcgis-rest-developer-credentials/src/updateApiKey.ts @@ -81,29 +81,31 @@ export async function updateApiKey( /** * step 2: update privileges and httpReferrers if provided. Build the object up to avoid overwriting any existing properties. */ - const getAppOption: IGetAppInfoOptions = { - ...baseRequestOptions, - authentication: requestOptions.authentication, - itemId: requestOptions.itemId - }; - const appResponse = await getRegisteredAppInfo(getAppOption); - const clientId = appResponse.client_id; - const options = appendCustomParams( - { ...appResponse, ...requestOptions }, // object with the custom params to look in - ["privileges", "httpReferrers"] // keys you want copied to the params object - ); - options.params.f = "json"; + if (requestOptions.privileges || requestOptions.httpReferrers) { + const getAppOption: IGetAppInfoOptions = { + ...baseRequestOptions, + authentication: requestOptions.authentication, + itemId: requestOptions.itemId + }; + const appResponse = await getRegisteredAppInfo(getAppOption); + const clientId = appResponse.client_id; + const options = appendCustomParams( + { ...appResponse, ...requestOptions }, // object with the custom params to look in + ["privileges", "httpReferrers"] // keys you want copied to the params object + ); + options.params.f = "json"; - // encode special params value (e.g. array type...) in advance in order to make encodeQueryString() works correctly - stringifyArrays(options); + // encode special params value (e.g. array type...) in advance in order to make encodeQueryString() works correctly + stringifyArrays(options); - const url = getPortalUrl(options) + `/oauth2/apps/${clientId}/update`; + const url = getPortalUrl(options) + `/oauth2/apps/${clientId}/update`; - // Raw response from `/oauth2/apps/${clientId}/update`, apiKey not included because key is same. - const updateResponse: IRegisteredAppResponse = await request(url, { - ...options, - authentication: requestOptions.authentication - }); + // Raw response from `/oauth2/apps/${clientId}/update`, apiKey not included because key is same. + const updateResponse: IRegisteredAppResponse = await request(url, { + ...options, + authentication: requestOptions.authentication + }); + } /** * step 3: get the updated item info to return to the user. diff --git a/packages/arcgis-rest-developer-credentials/test/invalidateApiKey.test.ts b/packages/arcgis-rest-developer-credentials/test/invalidateApiKey.test.ts new file mode 100644 index 000000000..e6df19bb5 --- /dev/null +++ b/packages/arcgis-rest-developer-credentials/test/invalidateApiKey.test.ts @@ -0,0 +1,110 @@ +import { invalidateApiKey } from "../src/invalidateApiKey.js"; +import fetchMock from "fetch-mock"; +import { IItem } from "@esri/arcgis-rest-portal"; +import { IRegisteredAppResponse } from "../src/shared/types/appType.js"; +import { TOMORROW } from "../../../scripts/test-helpers.js"; +import { ArcGISIdentityManager } from "@esri/arcgis-rest-request"; + +function setFetchMockPOSTFormUrlencoded( + url: string, + responseBody: any, + status: number, + routeName: string, + repeat: number +): void { + fetchMock.mock( + { + url: url, // url should match + method: "POST", // http method should match + headers: { "Content-Type": "application/x-www-form-urlencoded" }, // content type should match + name: routeName, + repeat: repeat + }, + { + body: responseBody, + status: status, + headers: { "Content-Type": "application/json" } + } + ); +} + +const mockGetAppInfoResponse: IRegisteredAppResponse = { + itemId: "cddcacee5848488bb981e6c6ff91ab79", + client_id: "EiwLuFlkNwE2Ifye", + client_secret: "dc7526de9ece482dba4704618fd3de81", + appType: "apikey", + redirect_uris: [], + registered: 1687824330000, + modified: 1687824330000, + apnsProdCert: null, + apnsSandboxCert: null, + gcmApiKey: null, + httpReferrers: [], + privileges: ["premium:user:geocode:temporary"], + isBeta: false, + isPersonalAPIToken: false, + apiToken1Active: true, + apiToken2Active: false, + customAppLoginShowTriage: false +}; + +const mockInvaildateApiKeyResponse = { + success: true +}; + +describe("invalidateApiKey", () => { + // setup IdentityManager + let MOCK_USER_SESSION: ArcGISIdentityManager; + + beforeAll(function () { + MOCK_USER_SESSION = new ArcGISIdentityManager({ + username: "745062756", + password: "fake-password", + portal: "https://www.arcgis.com/sharing/rest", + token: "fake-token", + tokenExpires: TOMORROW + }); + }); + + afterEach(() => fetchMock.restore()); + + it("should invalidate an API key", async () => { + setFetchMockPOSTFormUrlencoded( + "https://www.arcgis.com/sharing/rest/content/users/745062756/items/cddcacee5848488bb981e6c6ff91ab79/registeredAppInfo", + mockGetAppInfoResponse, + 200, + "getAppRoute", + 1 + ); + + setFetchMockPOSTFormUrlencoded( + "https://www.arcgis.com/sharing/rest/oauth2/revokeToken", + mockInvaildateApiKeyResponse, + 200, + "invalidateKeyRoute", + 1 + ); + + const response = await invalidateApiKey({ + itemId: "cddcacee5848488bb981e6c6ff91ab79", + apiKey: 1, + authentication: MOCK_USER_SESSION + }); + + // verify first fetch + expect(fetchMock.called("invalidateKeyRoute")).toBe(true); + const actualOptionGetAppRoute = fetchMock.lastOptions("invalidateKeyRoute"); + expect(actualOptionGetAppRoute.body).toContain("f=json"); + expect(actualOptionGetAppRoute.body).not.toContain("token=fake-token"); + expect(actualOptionGetAppRoute.body).toContain( + "client_id=EiwLuFlkNwE2Ifye" + ); + expect(actualOptionGetAppRoute.body).toContain( + "client_secret=dc7526de9ece482dba4704618fd3de81" + ); + + expect(response).toEqual({ + success: true + }); + }); +}); diff --git a/packages/arcgis-rest-developer-credentials/test/shared/helpers.test.ts b/packages/arcgis-rest-developer-credentials/test/shared/helpers.test.ts index d2db79d30..6af22a889 100644 --- a/packages/arcgis-rest-developer-credentials/test/shared/helpers.test.ts +++ b/packages/arcgis-rest-developer-credentials/test/shared/helpers.test.ts @@ -1,50 +1,98 @@ -import { buildExpirationDateParams } from "../../src/shared/helpers.js"; +import { + buildExpirationDateParams, + slotForKey, + slotForInvalidationKey +} from "../../src/shared/helpers.js"; -describe("buildExpirationDateParams", () => { - it("should return an object with expiration date 1 params", () => { - const expiration1 = new Date(); - const expirationDateParams = buildExpirationDateParams({ - apiToken1ExpirationDate: expiration1 +describe("helpers", () => { + describe("buildExpirationDateParams", () => { + it("should return an object with expiration date 1 params", () => { + const expiration1 = new Date(); + const expirationDateParams = buildExpirationDateParams({ + apiToken1ExpirationDate: expiration1 + }); + expect(expirationDateParams).toEqual({ + apiToken1ExpirationDate: expiration1 + }); }); - expect(expirationDateParams).toEqual({ - apiToken1ExpirationDate: expiration1 + + it("should return an object with expiration date 2 params", () => { + const expiration2 = new Date(); + const expirationDateParams = buildExpirationDateParams({ + apiToken2ExpirationDate: expiration2 + }); + expect(expirationDateParams).toEqual({ + apiToken2ExpirationDate: expiration2 + }); }); - }); - it("should return an object with expiration date 2 params", () => { - const expiration2 = new Date(); - const expirationDateParams = buildExpirationDateParams({ - apiToken2ExpirationDate: expiration2 + it("should return an object with both expiration date 1 and 2 params", () => { + const expiration1 = new Date(); + const expiration2 = new Date(); + const expirationDateParams = buildExpirationDateParams({ + apiToken1ExpirationDate: expiration1, + apiToken2ExpirationDate: expiration2 + }); + expect(expirationDateParams).toEqual({ + apiToken1ExpirationDate: expiration1, + apiToken2ExpirationDate: expiration2 + }); }); - expect(expirationDateParams).toEqual({ - apiToken2ExpirationDate: expiration2 + + it("should fill with a default of -1 when requested", () => { + const expirationDateParams = buildExpirationDateParams( + { + apiToken1ExpirationDate: undefined, + apiToken2ExpirationDate: undefined + }, + true + ); + expect(expirationDateParams).toEqual({ + apiToken1ExpirationDate: -1, + apiToken2ExpirationDate: -1 + }); }); }); - it("should return an object with both expiration date 1 and 2 params", () => { - const expiration1 = new Date(); - const expiration2 = new Date(); - const expirationDateParams = buildExpirationDateParams({ - apiToken1ExpirationDate: expiration1, - apiToken2ExpirationDate: expiration2 + describe("slotForKey", () => { + it("should return 1 for a key that contains AT1 in the proper spot", () => { + expect( + slotForKey( + "AAPTxy6BH1VEsoebNVZXo8HuiIOamKnP-TQacNgPnfkapJPNaUVBDrKX4IISTum7uUKCxustN-33gZ3OIputBuLHf-gu5Bdmw6A4S16pQ5UClfu79W13VBLYaqh3wjRnsCTmO8Q__TiGbXzwote3Z8AcbTMPPQoGqxeV6Z-vr2TQoQHzeLzfJAZzoNrkkTXM9AfYA-dBNrW_eBV9Zl0IYXXNXTR0OQWUZ3PQ5C5OInjh9OU.AT1_G1kye1HB" + ) + ).toEqual(1); + }); + + it("should return 2 for a key that contains AT2 in the proper spot", () => { + expect( + slotForKey( + "AAPTxy6BH1VEsoebNVZXo8HuiIOamKnP-TQacNgPnfkapJPNaUVBDrKX4IISTum7uUKCxustN-33gZ3OIputBuLHf-gu5Bdmw6A4S16pQ5UClfu79W13VBLYaqh3wjRnsCTmO8Q__TiGbXzwote3Z8AcbTMPPQoGqxeV6Z-vr2TQoQHzeLzfJAZzoNrkkTXM9AfYA-dBNrW_eBV9Zl0IYXXNXTR0OQWUZ3PQ5C5OInjh9OU.AT2_G1kye1HB" + ) + ).toEqual(2); }); - expect(expirationDateParams).toEqual({ - apiToken1ExpirationDate: expiration1, - apiToken2ExpirationDate: expiration2 + + it("should return undefined for a non api key string", () => { + expect(slotForKey("foo")).toEqual(undefined); }); }); - it("should fill with a default of -1 when requested", () => { - const expirationDateParams = buildExpirationDateParams( - { - apiToken1ExpirationDate: undefined, - apiToken2ExpirationDate: undefined - }, - true - ); - expect(expirationDateParams).toEqual({ - apiToken1ExpirationDate: -1, - apiToken2ExpirationDate: -1 + describe("slotForInvalidationKey", () => { + it("should return 1 for a full key that contains AT1 in the proper spot", () => { + expect( + slotForInvalidationKey( + "AAPTxy6BH1VEsoebNVZXo8HuiIOamKnP-TQacNgPnfkapJPNaUVBDrKX4IISTum7uUKCxustN-33gZ3OIputBuLHf-gu5Bdmw6A4S16pQ5UClfu79W13VBLYaqh3wjRnsCTmO8Q__TiGbXzwote3Z8AcbTMPPQoGqxeV6Z-vr2TQoQHzeLzfJAZzoNrkkTXM9AfYA-dBNrW_eBV9Zl0IYXXNXTR0OQWUZ3PQ5C5OInjh9OU.AT1_G1kye1HB" + ) + ).toEqual(1); + }); + + it("should return undefined for a non api key string", () => { + expect(slotForInvalidationKey("foo")).toEqual(undefined); + }); + + it("should return 1 or 2 if passed", () => { + expect(slotForInvalidationKey(1)).toEqual(1); + expect(slotForInvalidationKey(2)).toEqual(2); + expect(slotForInvalidationKey(3 as any)).toEqual(undefined); }); }); }); diff --git a/packages/arcgis-rest-developer-credentials/test/updateApiKey.test.ts b/packages/arcgis-rest-developer-credentials/test/updateApiKey.test.ts index c6b35c250..590dd87d0 100644 --- a/packages/arcgis-rest-developer-credentials/test/updateApiKey.test.ts +++ b/packages/arcgis-rest-developer-credentials/test/updateApiKey.test.ts @@ -5,7 +5,6 @@ import { IItem } from "@esri/arcgis-rest-portal"; import { IRegisteredAppResponse } from "../src/shared/types/appType.js"; import { IApiKeyResponse } from "../src/shared/types/apiKeyType.js"; import { updateApiKey } from "../src/updateApiKey.js"; -import { Privileges } from "../src/shared/enum/privileges.js"; function setFetchMockPOSTFormUrlencoded( url: string, @@ -410,4 +409,92 @@ describe("updateApiKey()", () => { // verify func return expect(updateApiKeyResponse).toEqual(keyResponseExpectedNoParams); }); + + it("should update an API key without privileges or referers", async function () { + let callCount = 0; + fetchMock.mock( + { + url: "https://www.arcgis.com/sharing/rest/content/users/3807206777/items/4832e6bc5fa540129822212c12168710/registeredAppInfo", // url should match + method: "POST", // http method should match + headers: { "Content-Type": "application/x-www-form-urlencoded" }, // content type should match + name: "getAppRoute" + }, + function () { + if (callCount === 0) { + callCount++; + return { + body: mockGetAppResponseNoParams, + status: 200, + headers: { "Content-Type": "application/json" } + }; + } + return { + body: mockUpdatedGetAppResponseNoParams, + status: 200, + headers: { "Content-Type": "application/json" } + }; + } + ); + + // step 1 update item with expiration dates + setFetchMockPOSTFormUrlencoded( + "https://www.arcgis.com/sharing/rest/content/users/3807206777/items/4832e6bc5fa540129822212c12168710/update", + updateItemResponseNoParams, + 200, + "updateItemRoute", + 1 + ); + + // step 2 update registered app + setFetchMockPOSTFormUrlencoded( + "https://www.arcgis.com/sharing/rest/oauth2/apps/ShnJhKhzr2cVCujl/update", + mockUpdateKeyResponseNoParams, + 200, + "updateKeyRoute", + 1 + ); + + // step 3 get item info + setFetchMockPOSTFormUrlencoded( + "https://www.arcgis.com/sharing/rest/content/items/4832e6bc5fa540129822212c12168710", + mockGetItemResponseNoParams, + 200, + "getItemRoute", + 1 + ); + + // step 5 generate access token 2 + setFetchMockPOSTFormUrlencoded( + "https://www.arcgis.com/sharing/rest/oauth2/token", + generateAccessToken2ParamsResponse, + 200, + "generateToken1Route", + 1 + ); + + const updateApiKeyResponse = await updateApiKey({ + itemId: "4832e6bc5fa540129822212c12168710", + authentication: authOnline, + generateToken2: true, + apiToken2ExpirationDate: TOMORROW + }); + + // verify first fetch + expect(fetchMock.called("getAppRoute")).toBe(true); + const actualOptionGetAppRoute = fetchMock.lastOptions("getAppRoute"); + expect(actualOptionGetAppRoute.body).toContain("f=json"); + expect(actualOptionGetAppRoute.body).toContain("token=fake-token"); + + // verify second fetch + expect(fetchMock.called("updateKeyRoute")).toBe(false); + + // verify third fetch + expect(fetchMock.called("getItemRoute")).toBe(true); + const actualOptionGetItemRoute = fetchMock.lastOptions("getItemRoute"); + expect(actualOptionGetItemRoute.body).toContain("f=json"); + expect(actualOptionGetItemRoute.body).toContain("token=fake-token"); + + // verify func return + expect(updateApiKeyResponse).toEqual(keyResponseExpectedNoParams); + }); }); From 177849e0f5c80fd02e605da5e9fec73c5b1e249c Mon Sep 17 00:00:00 2001 From: Patrick Arlt Date: Wed, 26 Feb 2025 10:26:31 -0800 Subject: [PATCH 2/3] docs(arcgis-rest-developer-credentials): update examples --- .../src/createApiKey.ts | 10 ++++++++-- .../src/shared/registerApp.ts | 2 +- .../src/updateApiKey.ts | 10 ++++++++-- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/arcgis-rest-developer-credentials/src/createApiKey.ts b/packages/arcgis-rest-developer-credentials/src/createApiKey.ts index 459f16871..f18c3f360 100644 --- a/packages/arcgis-rest-developer-credentials/src/createApiKey.ts +++ b/packages/arcgis-rest-developer-credentials/src/createApiKey.ts @@ -37,14 +37,20 @@ import { getRegisteredAppInfo } from "./shared/getRegisteredAppInfo.js"; * password: "xyz_pw" * }); * + * const threeDaysFromToday = new Date(); + * threeDaysFromToday.setDate(threeDaysFromToday.getDate() + 3); + * threeDaysFromToday.setHours(23, 59, 59, 999); + * * createApiKey({ * title: "xyz_title", * description: "xyz_desc", * tags: ["xyz_tag1", "xyz_tag2"], * privileges: ["premium:user:networkanalysis:routing"], - * authentication: authSession + * authentication: authSession, + * generateToken1: true, // optional,generate a new token + * apiToken1ExpirationDate: threeDaysFromToday // optional, update expiration date * }).then((registeredAPIKey: IApiKeyResponse) => { - * // => {apiKey: "xyz_key", item: {tags: ["xyz_tag1", "xyz_tag2"], ...}, ...} + * // => {accessToken1: "xyz_key", item: {tags: ["xyz_tag1", "xyz_tag2"], ...}, ...} * }).catch(e => { * // => an exception object * }); diff --git a/packages/arcgis-rest-developer-credentials/src/shared/registerApp.ts b/packages/arcgis-rest-developer-credentials/src/shared/registerApp.ts index 9a88e84e8..c587af7f6 100644 --- a/packages/arcgis-rest-developer-credentials/src/shared/registerApp.ts +++ b/packages/arcgis-rest-developer-credentials/src/shared/registerApp.ts @@ -34,7 +34,7 @@ import { stringifyArrays, registeredAppResponseToApp } from "./helpers.js"; * appType: "multiple", * redirect_uris: ["http://localhost:3000/"], * httpReferrers: ["http://localhost:3000/"], - * privileges: [Privileges.Geocode, Privileges.FeatureReport], + * privileges: ["premium:user:geocode:temporary", Privileges.FeatureReport], * authentication: authSession * }).then((registeredApp: IApp) => { * // => {client_id: "xyz_id", client_secret: "xyz_secret", ...} diff --git a/packages/arcgis-rest-developer-credentials/src/updateApiKey.ts b/packages/arcgis-rest-developer-credentials/src/updateApiKey.ts index 1501ffcd0..66beda86d 100644 --- a/packages/arcgis-rest-developer-credentials/src/updateApiKey.ts +++ b/packages/arcgis-rest-developer-credentials/src/updateApiKey.ts @@ -39,13 +39,19 @@ import { * password: "xyz_pw" * }); * + * const threeDaysFromToday = new Date(); + * threeDaysFromToday.setDate(threeDaysFromToday.getDate() + 3); + * threeDaysFromToday.setHours(23, 59, 59, 999); + * * updateApiKey({ * itemId: "xyz_itemId", - * privileges: [Privileges.Geocode], + * privileges: ["premium:user:geocode:temporary"], * httpReferrers: [], // httpReferrers will be set to be empty * authentication: authSession + * generateToken1: true, // optional,generate a new token + * apiToken1ExpirationDate: threeDaysFromToday // optional, update expiration date * }).then((updatedAPIKey: IApiKeyResponse) => { - * // => {apiKey: "xyz_key", item: {tags: ["xyz_tag1", "xyz_tag2"], ...}, ...} + * // => {accessToken1: "xyz_key", item: {tags: ["xyz_tag1", "xyz_tag2"], ...}, ...} * }).catch(e => { * // => an exception object * }); From 403daa90c7fc75c5cdfbaae1ba2ed869441028da Mon Sep 17 00:00:00 2001 From: Patrick Arlt Date: Wed, 26 Feb 2025 10:43:56 -0800 Subject: [PATCH 3/3] docs(arcgis-rest-developer-credentials): update invalidate API key example --- .../src/invalidateApiKey.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/arcgis-rest-developer-credentials/src/invalidateApiKey.ts b/packages/arcgis-rest-developer-credentials/src/invalidateApiKey.ts index 100557af0..3fd0261fb 100644 --- a/packages/arcgis-rest-developer-credentials/src/invalidateApiKey.ts +++ b/packages/arcgis-rest-developer-credentials/src/invalidateApiKey.ts @@ -12,6 +12,19 @@ import { slotForInvalidationKey } from "./shared/helpers.js"; /** * Used to invalidate an API key. + * + * ```js + * import { invalidateApiKey } from "@esri/arcgis-rest-developer-credentials"; + * + * invalidateApiKey({ + * itemId: ITEM_ID, + * authentication, + * apiKey: 1, // invalidate the key in slot 1 + * }).then((response) => { + * // => {success: true} + * }).catch(e => { + * // => an exception object + * }); */ export async function invalidateApiKey( requestOptions: IInvalidateApiKeyOptions