@credal/actions 0.2.223 → 0.2.225
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/actionMapper.js +29 -1
- package/dist/actions/autogen/templates.d.ts +4 -0
- package/dist/actions/autogen/templates.js +385 -4
- package/dist/actions/autogen/types.d.ts +379 -0
- package/dist/actions/autogen/types.js +161 -4
- package/dist/actions/providers/ashby/addCandidateToProject.d.ts +3 -0
- package/dist/actions/providers/ashby/addCandidateToProject.js +31 -0
- package/dist/actions/providers/ashby/createCandidate.d.ts +3 -0
- package/dist/actions/providers/ashby/createCandidate.js +38 -0
- package/dist/actions/providers/ashby/createNote.d.ts +3 -0
- package/dist/actions/providers/ashby/createNote.js +31 -0
- package/dist/actions/providers/ashby/getCandidateInfo.d.ts +3 -0
- package/dist/actions/providers/ashby/getCandidateInfo.js +33 -0
- package/dist/actions/providers/ashby/listCandidateNotes.d.ts +3 -0
- package/dist/actions/providers/ashby/listCandidateNotes.js +33 -0
- package/dist/actions/providers/ashby/listCandidates.d.ts +3 -0
- package/dist/actions/providers/ashby/listCandidates.js +30 -0
- package/dist/actions/providers/ashby/searchCandidates.d.ts +3 -0
- package/dist/actions/providers/ashby/searchCandidates.js +34 -0
- package/dist/actions/providers/ashby/updateCandidate.d.ts +3 -0
- package/dist/actions/providers/ashby/updateCandidate.js +56 -0
- package/dist/actions/providers/github/searchRepository.js +7 -7
- package/dist/actions/providers/gong/getGongTranscripts.d.ts +3 -0
- package/dist/actions/providers/gong/getGongTranscripts.js +315 -0
- package/dist/actions/providers/google-oauth/listFilesWithAssignedComments.d.ts +3 -0
- package/dist/actions/providers/google-oauth/listFilesWithAssignedComments.js +41 -0
- package/dist/actions/providers/jamf/getJamfComputerInventory.d.ts +3 -0
- package/dist/actions/providers/jamf/getJamfComputerInventory.js +37 -0
- package/dist/actions/providers/jamf/getJamfFileVaultRecoveryKey.d.ts +3 -0
- package/dist/actions/providers/jamf/getJamfFileVaultRecoveryKey.js +39 -0
- package/dist/actions/providers/jamf/getJamfUserComputerId.d.ts +3 -0
- package/dist/actions/providers/jamf/getJamfUserComputerId.js +45 -0
- package/dist/actions/providers/jamf/lockJamfComputerById.d.ts +3 -0
- package/dist/actions/providers/jamf/lockJamfComputerById.js +39 -0
- package/dist/actions/providers/jira/updateServiceDeskRequest.d.ts +3 -0
- package/dist/actions/providers/jira/updateServiceDeskRequest.js +72 -0
- package/dist/actions/providers/kandji/getFVRecoveryKeyForDevice.d.ts +3 -0
- package/dist/actions/providers/kandji/getFVRecoveryKeyForDevice.js +85 -0
- package/dist/actions/providers/microsoft/createDocument.js +3 -4
- package/dist/actions/providers/microsoft/getDocument.js +3 -6
- package/dist/actions/providers/microsoft/getSharepointItem.d.ts +3 -0
- package/dist/actions/providers/microsoft/getSharepointItem.js +61 -0
- package/dist/actions/providers/microsoft/listSharepointFolder.d.ts +3 -0
- package/dist/actions/providers/microsoft/listSharepointFolder.js +92 -0
- package/dist/actions/providers/microsoft/readSharepointContent.d.ts +3 -0
- package/dist/actions/providers/microsoft/readSharepointContent.js +157 -0
- package/dist/actions/providers/microsoft/searchSharepoint.d.ts +3 -0
- package/dist/actions/providers/microsoft/searchSharepoint.js +147 -0
- package/dist/actions/providers/microsoft/sendOutlookEmail.d.ts +3 -0
- package/dist/actions/providers/microsoft/sendOutlookEmail.js +48 -0
- package/dist/actions/providers/microsoft/sharepointUtils.d.ts +56 -0
- package/dist/actions/providers/microsoft/sharepointUtils.js +153 -0
- package/dist/actions/providers/microsoft/updateDocument.js +3 -6
- package/dist/actions/providers/microsoft/updateSpreadsheet.js +3 -4
- package/dist/actions/providers/microsoft/utils.d.ts +10 -0
- package/dist/actions/providers/microsoft/utils.js +9 -0
- package/dist/actions/providers/okta/addUserToGroup.d.ts +3 -0
- package/dist/actions/providers/okta/addUserToGroup.js +56 -0
- package/dist/actions/providers/okta/getOktaGroup.d.ts +3 -0
- package/dist/actions/providers/okta/getOktaGroup.js +73 -0
- package/dist/actions/providers/okta/getOktaUser.d.ts +3 -0
- package/dist/actions/providers/okta/getOktaUser.js +40 -0
- package/dist/actions/providers/okta/listMFA.d.ts +3 -0
- package/dist/actions/providers/okta/listMFA.js +52 -0
- package/dist/actions/providers/okta/listOktaGroupMembers.d.ts +3 -0
- package/dist/actions/providers/okta/listOktaGroupMembers.js +99 -0
- package/dist/actions/providers/okta/listOktaGroups.d.ts +3 -0
- package/dist/actions/providers/okta/listOktaGroups.js +92 -0
- package/dist/actions/providers/okta/listOktaUserGroups.d.ts +3 -0
- package/dist/actions/providers/okta/listOktaUserGroups.js +83 -0
- package/dist/actions/providers/okta/listOktaUsers.d.ts +3 -0
- package/dist/actions/providers/okta/listOktaUsers.js +100 -0
- package/dist/actions/providers/okta/removeUserFromGroup.d.ts +3 -0
- package/dist/actions/providers/okta/removeUserFromGroup.js +56 -0
- package/dist/actions/providers/okta/resetMFA.d.ts +3 -0
- package/dist/actions/providers/okta/resetMFA.js +62 -0
- package/dist/actions/providers/okta/resetPassword.d.ts +3 -0
- package/dist/actions/providers/okta/resetPassword.js +57 -0
- package/dist/actions/providers/okta/triggerOktaWorkflow.d.ts +3 -0
- package/dist/actions/providers/okta/triggerOktaWorkflow.js +44 -0
- package/dist/actions/providers/salesforce/fetchSalesforceSchema.d.ts +3 -0
- package/dist/actions/providers/salesforce/fetchSalesforceSchema.js +40 -0
- package/package.json +2 -2
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export declare const MISSING_SITES_SCOPE_MESSAGE = "Missing Sites.Read.All: this token only permits file/folder access. Site, page, and site-wide search operations require re-authentication with sites scopes.";
|
|
2
|
+
/**
|
|
3
|
+
* Encodes a URL into a Microsoft Graph share token usable with the /shares endpoint:
|
|
4
|
+
* "u!" + unpadded base64url of the URL.
|
|
5
|
+
*/
|
|
6
|
+
export declare function encodeShareUrl(url: string): string;
|
|
7
|
+
export type SharepointDriveItem = {
|
|
8
|
+
id?: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
webUrl?: string;
|
|
11
|
+
size?: number;
|
|
12
|
+
lastModifiedDateTime?: string;
|
|
13
|
+
file?: {
|
|
14
|
+
mimeType?: string;
|
|
15
|
+
};
|
|
16
|
+
folder?: {
|
|
17
|
+
childCount?: number;
|
|
18
|
+
};
|
|
19
|
+
parentReference?: {
|
|
20
|
+
driveId?: string;
|
|
21
|
+
siteId?: string;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
export type ResolvedSharepointItem = {
|
|
25
|
+
itemType: "file" | "folder";
|
|
26
|
+
driveId: string;
|
|
27
|
+
itemId: string;
|
|
28
|
+
siteId?: string;
|
|
29
|
+
name?: string;
|
|
30
|
+
webUrl?: string;
|
|
31
|
+
mimeType?: string;
|
|
32
|
+
sizeBytes?: number;
|
|
33
|
+
} | {
|
|
34
|
+
itemType: "site";
|
|
35
|
+
siteId: string;
|
|
36
|
+
name?: string;
|
|
37
|
+
webUrl?: string;
|
|
38
|
+
} | {
|
|
39
|
+
itemType: "page";
|
|
40
|
+
siteId: string;
|
|
41
|
+
pageId?: string;
|
|
42
|
+
name?: string;
|
|
43
|
+
webUrl?: string;
|
|
44
|
+
};
|
|
45
|
+
export declare function isSiteScopeError(error: unknown): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Resolves any pasted SharePoint/OneDrive URL (file, folder, Office web-viewer link, bare site,
|
|
48
|
+
* subsite, root site, site page, OneDrive share link) to Graph identifiers.
|
|
49
|
+
*
|
|
50
|
+
* Resolution order: SitePages URLs go to the Pages API; everything else probes /shares first
|
|
51
|
+
* (files/folders, cheap, Files.Read.All only), then falls back to site resolution on any 4xx —
|
|
52
|
+
* URL shape cannot distinguish a subsite from a folder path, so probing is the only correct
|
|
53
|
+
* strategy. Throws Error(MISSING_SITES_SCOPE_MESSAGE) when a site-level call fails with 401/403;
|
|
54
|
+
* when both probes fail, throws a combined error naming both statuses.
|
|
55
|
+
*/
|
|
56
|
+
export declare function resolveItemFromUrl(authToken: string, url: string): Promise<ResolvedSharepointItem>;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { ApiError, axiosClient } from "../../util/axiosClient.js";
|
|
2
|
+
import { MICROSOFT_GRAPH_API_URL } from "./utils.js";
|
|
3
|
+
export const MISSING_SITES_SCOPE_MESSAGE = "Missing Sites.Read.All: this token only permits file/folder access. Site, page, and site-wide search operations require re-authentication with sites scopes.";
|
|
4
|
+
/**
|
|
5
|
+
* Encodes a URL into a Microsoft Graph share token usable with the /shares endpoint:
|
|
6
|
+
* "u!" + unpadded base64url of the URL.
|
|
7
|
+
*/
|
|
8
|
+
export function encodeShareUrl(url) {
|
|
9
|
+
const base64 = Buffer.from(url, "utf-8").toString("base64");
|
|
10
|
+
return "u!" + base64.replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
11
|
+
}
|
|
12
|
+
export function isSiteScopeError(error) {
|
|
13
|
+
return error instanceof ApiError && (error.status === 401 || error.status === 403);
|
|
14
|
+
}
|
|
15
|
+
/** GET /sites/{hostname}:{serverRelativePath} — requires Sites.Read.All. */
|
|
16
|
+
async function getSiteByPath(hostname, serverRelativePath, headers) {
|
|
17
|
+
const path = serverRelativePath.replace(/\/+$/, "");
|
|
18
|
+
// An empty path means the tenant's root site, which Graph addresses without the :path suffix
|
|
19
|
+
const requestUrl = path
|
|
20
|
+
? `${MICROSOFT_GRAPH_API_URL}/sites/${hostname}:${path}`
|
|
21
|
+
: `${MICROSOFT_GRAPH_API_URL}/sites/${hostname}`;
|
|
22
|
+
try {
|
|
23
|
+
const response = await axiosClient.get(requestUrl, { headers });
|
|
24
|
+
return response.data;
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
// Capability is detected by attempting the call: a 401/403 here means the token lacks site scopes
|
|
28
|
+
if (isSiteScopeError(error)) {
|
|
29
|
+
throw new Error(MISSING_SITES_SCOPE_MESSAGE, { cause: error });
|
|
30
|
+
}
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Finds a site page's ID by its file name (e.g. "Home.aspx") — requires Sites.Read.All. */
|
|
35
|
+
async function findSitePageByName(siteId, pageName, headers) {
|
|
36
|
+
const pagesUrl = `${MICROSOFT_GRAPH_API_URL}/sites/${siteId}/pages/microsoft.graph.sitePage`;
|
|
37
|
+
try {
|
|
38
|
+
// Try a server-side filter first to avoid enumerating every page in the site.
|
|
39
|
+
// Caveat: the docs list $filter as supported, but Graph is known to silently
|
|
40
|
+
// ignore name filters on this endpoint, so treat the response defensively.
|
|
41
|
+
const escapedName = pageName.replace(/'/g, "''");
|
|
42
|
+
const filtered = await axiosClient.get(`${pagesUrl}?$filter=${encodeURIComponent(`name eq '${escapedName}'`)}`, { headers });
|
|
43
|
+
const match = (filtered.data.value ?? []).find(page => page.name?.toLowerCase() === pageName.toLowerCase());
|
|
44
|
+
if (match != null)
|
|
45
|
+
return match;
|
|
46
|
+
// No match and no further pages: whether the filter was honored (empty result) or
|
|
47
|
+
// ignored (we saw the complete unfiltered set), the page isn't there.
|
|
48
|
+
if (filtered.data["@odata.nextLink"] == null)
|
|
49
|
+
return undefined;
|
|
50
|
+
// No match but more pages exist — the filter was likely ignored (a known Graph gap
|
|
51
|
+
// for sitePage name filtering); fall through to full enumeration.
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (isSiteScopeError(error)) {
|
|
55
|
+
throw new Error(MISSING_SITES_SCOPE_MESSAGE, { cause: error });
|
|
56
|
+
}
|
|
57
|
+
// Some tenants reject $filter on this endpoint outright — fall back to enumeration
|
|
58
|
+
if (!(error instanceof ApiError && error.status === 400)) {
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
let nextUrl = `${pagesUrl}?$top=100`;
|
|
63
|
+
try {
|
|
64
|
+
while (nextUrl) {
|
|
65
|
+
const response = await axiosClient.get(nextUrl, { headers });
|
|
66
|
+
const pages = response.data.value ?? [];
|
|
67
|
+
const match = pages.find(page => page.name?.toLowerCase() === pageName.toLowerCase());
|
|
68
|
+
if (match != null)
|
|
69
|
+
return match;
|
|
70
|
+
nextUrl = response.data["@odata.nextLink"];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (isSiteScopeError(error)) {
|
|
75
|
+
throw new Error(MISSING_SITES_SCOPE_MESSAGE, { cause: error });
|
|
76
|
+
}
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Resolves any pasted SharePoint/OneDrive URL (file, folder, Office web-viewer link, bare site,
|
|
83
|
+
* subsite, root site, site page, OneDrive share link) to Graph identifiers.
|
|
84
|
+
*
|
|
85
|
+
* Resolution order: SitePages URLs go to the Pages API; everything else probes /shares first
|
|
86
|
+
* (files/folders, cheap, Files.Read.All only), then falls back to site resolution on any 4xx —
|
|
87
|
+
* URL shape cannot distinguish a subsite from a folder path, so probing is the only correct
|
|
88
|
+
* strategy. Throws Error(MISSING_SITES_SCOPE_MESSAGE) when a site-level call fails with 401/403;
|
|
89
|
+
* when both probes fail, throws a combined error naming both statuses.
|
|
90
|
+
*/
|
|
91
|
+
export async function resolveItemFromUrl(authToken, url) {
|
|
92
|
+
const headers = { Authorization: `Bearer ${authToken}` };
|
|
93
|
+
const parsed = new URL(url);
|
|
94
|
+
// Site pages: https://{tenant}.sharepoint.com/sites/{site}/SitePages/{page}.aspx
|
|
95
|
+
const sitePageMatch = parsed.pathname.match(/^(.*)\/SitePages\/([^/]+\.aspx)$/i);
|
|
96
|
+
if (sitePageMatch) {
|
|
97
|
+
const [, sitePath, pageFileName] = sitePageMatch;
|
|
98
|
+
const pageName = decodeURIComponent(pageFileName);
|
|
99
|
+
const site = await getSiteByPath(parsed.hostname, sitePath, headers);
|
|
100
|
+
const page = await findSitePageByName(site.id, pageName, headers);
|
|
101
|
+
return {
|
|
102
|
+
itemType: "page",
|
|
103
|
+
siteId: site.id,
|
|
104
|
+
pageId: page?.id,
|
|
105
|
+
name: page?.title ?? page?.name ?? pageName,
|
|
106
|
+
webUrl: page?.webUrl ?? url,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// The /shares resolver handles files, folders, Doc.aspx web-viewer links, OneDrive personal
|
|
110
|
+
// and 1drv.ms share links with Files.Read.All alone
|
|
111
|
+
let shareError;
|
|
112
|
+
try {
|
|
113
|
+
const response = await axiosClient.get(`${MICROSOFT_GRAPH_API_URL}/shares/${encodeShareUrl(url)}/driveItem`, {
|
|
114
|
+
headers,
|
|
115
|
+
});
|
|
116
|
+
const item = response.data;
|
|
117
|
+
if (item?.id && item?.parentReference?.driveId) {
|
|
118
|
+
return {
|
|
119
|
+
itemType: item.folder ? "folder" : "file",
|
|
120
|
+
driveId: item.parentReference.driveId,
|
|
121
|
+
itemId: item.id,
|
|
122
|
+
siteId: item.parentReference.siteId,
|
|
123
|
+
name: item.name,
|
|
124
|
+
webUrl: item.webUrl,
|
|
125
|
+
mimeType: item.file?.mimeType,
|
|
126
|
+
sizeBytes: item.size,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
throw new Error(`Unable to resolve URL to a drive item: ${url}`);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
// A site is not a drive item, so /shares rejects site URLs with a 4xx. URL shape cannot
|
|
133
|
+
// distinguish a subsite from a folder path, so we always probe /shares first and, on any
|
|
134
|
+
// client error, fall back to resolving the path as a site. 5xx/network errors are real
|
|
135
|
+
// failures and are rethrown immediately.
|
|
136
|
+
if (!(error instanceof ApiError && error.status !== undefined && error.status < 500)) {
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
shareError = error;
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const site = await getSiteByPath(parsed.hostname, parsed.pathname, headers);
|
|
143
|
+
return { itemType: "site", siteId: site.id, name: site.displayName, webUrl: site.webUrl };
|
|
144
|
+
}
|
|
145
|
+
catch (siteError) {
|
|
146
|
+
// A missing sites scope is the more actionable diagnosis — surface it as-is
|
|
147
|
+
if (siteError instanceof Error && siteError.message === MISSING_SITES_SCOPE_MESSAGE) {
|
|
148
|
+
throw siteError;
|
|
149
|
+
}
|
|
150
|
+
const siteStatus = siteError instanceof ApiError && siteError.status !== undefined ? siteError.status : "error";
|
|
151
|
+
throw new Error(`Unable to resolve URL: not a document or folder (/shares: ${shareError?.status ?? "error"}) and not a site (/sites: ${siteStatus}): ${url}`, { cause: siteError });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { getGraphClient } from "./utils.js";
|
|
1
|
+
import { getDrivePath, getGraphClient } from "./utils.js";
|
|
2
2
|
const updateDocument = async ({ params, authParams, }) => {
|
|
3
|
-
const { documentId, content, siteId } = params;
|
|
3
|
+
const { documentId, content, siteId, driveId } = params;
|
|
4
4
|
let client = undefined;
|
|
5
5
|
try {
|
|
6
6
|
client = await getGraphClient(authParams);
|
|
@@ -12,10 +12,7 @@ const updateDocument = async ({ params, authParams, }) => {
|
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
14
|
try {
|
|
15
|
-
|
|
16
|
-
const endpoint = siteId
|
|
17
|
-
? `/sites/${siteId}/drive/items/${documentId}/content`
|
|
18
|
-
: `/me/drive/items/${documentId}/content`;
|
|
15
|
+
const endpoint = `${getDrivePath({ driveId, siteId })}/items/${documentId}/content`;
|
|
19
16
|
const response = await client.api(endpoint).put(content);
|
|
20
17
|
return {
|
|
21
18
|
success: true,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { getGraphClient } from "./utils.js";
|
|
1
|
+
import { getDrivePath, getGraphClient } from "./utils.js";
|
|
2
2
|
const updateSpreadsheet = async ({ params, authParams, }) => {
|
|
3
|
-
const { spreadsheetId, range, values, siteId } = params;
|
|
3
|
+
const { spreadsheetId, range, values, siteId, driveId } = params;
|
|
4
4
|
let client = undefined;
|
|
5
5
|
try {
|
|
6
6
|
client = await getGraphClient(authParams);
|
|
@@ -11,7 +11,6 @@ const updateSpreadsheet = async ({ params, authParams, }) => {
|
|
|
11
11
|
error: "Error while authorizing: " + (error instanceof Error ? error.message : "Unknown error"),
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
|
-
const apiEndpointPrefix = siteId ? `/sites/${siteId}` : "/me";
|
|
15
14
|
if (!range.includes("!")) {
|
|
16
15
|
return {
|
|
17
16
|
success: false,
|
|
@@ -25,7 +24,7 @@ const updateSpreadsheet = async ({ params, authParams, }) => {
|
|
|
25
24
|
error: "Invalid range format. Both sheet name and cell range must be specified.",
|
|
26
25
|
};
|
|
27
26
|
}
|
|
28
|
-
const apiEndpoint = `${
|
|
27
|
+
const apiEndpoint = `${getDrivePath({ driveId, siteId })}/items/${spreadsheetId}/workbook/worksheets/${sheetName}/range(address='${cellRange}')`;
|
|
29
28
|
try {
|
|
30
29
|
const response = await client.api(apiEndpoint).patch({ values });
|
|
31
30
|
return {
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import { Client } from "@microsoft/microsoft-graph-client";
|
|
2
2
|
import type { AuthParamsType } from "../../autogen/types.js";
|
|
3
3
|
export declare function getGraphClient(authParams: AuthParamsType): Promise<Client>;
|
|
4
|
+
/**
|
|
5
|
+
* Builds the Graph API path prefix addressing a drive. Item IDs are scoped to a drive, so a
|
|
6
|
+
* driveId (when known) addresses the exact document library; /sites/{siteId}/drive only ever
|
|
7
|
+
* reaches the site's default library, and /me/drive the user's personal OneDrive.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getDrivePath({ driveId, siteId }: {
|
|
10
|
+
driveId?: string;
|
|
11
|
+
siteId?: string;
|
|
12
|
+
}): string;
|
|
4
13
|
/**
|
|
5
14
|
* Validates and sanitizes a filename for SharePoint or OneDrive.
|
|
6
15
|
* @param fileName The original filename to validate and sanitize.
|
|
7
16
|
* @returns A sanitized filename that is safe to use.
|
|
8
17
|
*/
|
|
9
18
|
export declare function validateAndSanitizeFileName(fileName: string): string;
|
|
19
|
+
export declare const MICROSOFT_GRAPH_API_URL = "https://graph.microsoft.com/v1.0";
|
|
@@ -10,6 +10,14 @@ export async function getGraphClient(authParams) {
|
|
|
10
10
|
},
|
|
11
11
|
});
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Builds the Graph API path prefix addressing a drive. Item IDs are scoped to a drive, so a
|
|
15
|
+
* driveId (when known) addresses the exact document library; /sites/{siteId}/drive only ever
|
|
16
|
+
* reaches the site's default library, and /me/drive the user's personal OneDrive.
|
|
17
|
+
*/
|
|
18
|
+
export function getDrivePath({ driveId, siteId }) {
|
|
19
|
+
return driveId ? `/drives/${driveId}` : siteId ? `/sites/${siteId}/drive` : "/me/drive";
|
|
20
|
+
}
|
|
13
21
|
/**
|
|
14
22
|
* Validates and sanitizes a filename for SharePoint or OneDrive.
|
|
15
23
|
* @param fileName The original filename to validate and sanitize.
|
|
@@ -33,3 +41,4 @@ export function validateAndSanitizeFileName(fileName) {
|
|
|
33
41
|
}
|
|
34
42
|
return sanitizedFileName;
|
|
35
43
|
}
|
|
44
|
+
export const MICROSOFT_GRAPH_API_URL = "https://graph.microsoft.com/v1.0";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { AxiosError } from "axios";
|
|
11
|
+
import { axiosClient } from "../../util/axiosClient.js";
|
|
12
|
+
const addUserToGroup = (_a) => __awaiter(void 0, [_a], void 0, function* ({ authParams, params, }) {
|
|
13
|
+
var _b, _c;
|
|
14
|
+
const { authToken, baseUrl } = authParams;
|
|
15
|
+
if (!authToken || !baseUrl) {
|
|
16
|
+
return {
|
|
17
|
+
success: false,
|
|
18
|
+
error: "Missing Okta OAuth token (authToken) or base URL (baseUrl) in authParams.",
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const requestConfig = {
|
|
23
|
+
headers: {
|
|
24
|
+
Authorization: `Bearer ${authToken}`,
|
|
25
|
+
Accept: "application/json",
|
|
26
|
+
"Content-Type": "application/json",
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
const endpointUrl = new URL(`/api/v1/groups/${params.groupId}/users/${params.userId}`, baseUrl).toString();
|
|
30
|
+
const response = yield axiosClient.put(endpointUrl, {}, requestConfig);
|
|
31
|
+
if (response.status === 204) {
|
|
32
|
+
return { success: true };
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
const errorDetail = ((_b = response.data) === null || _b === void 0 ? void 0 : _b.errorSummary) || ((_c = response.data) === null || _c === void 0 ? void 0 : _c.message) || `Okta API responded with status ${response.status}`;
|
|
36
|
+
return { success: false, error: `Failed to add user to group: ${errorDetail}` };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
console.error("Error adding user to group:", error);
|
|
41
|
+
let errorMessage = "Unknown error while adding user to group";
|
|
42
|
+
if (error instanceof AxiosError && error.response) {
|
|
43
|
+
const oktaError = error.response.data;
|
|
44
|
+
errorMessage =
|
|
45
|
+
(oktaError === null || oktaError === void 0 ? void 0 : oktaError.errorSummary) || (oktaError === null || oktaError === void 0 ? void 0 : oktaError.message) || `Okta API request failed with status ${error.response.status}`;
|
|
46
|
+
}
|
|
47
|
+
else if (error instanceof Error) {
|
|
48
|
+
errorMessage = error.message;
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
success: false,
|
|
52
|
+
error: errorMessage,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
export default addUserToGroup;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { AxiosError } from "axios";
|
|
11
|
+
import { axiosClient } from "../../util/axiosClient.js";
|
|
12
|
+
const getOktaGroup = (_a) => __awaiter(void 0, [_a], void 0, function* ({ authParams, params, }) {
|
|
13
|
+
var _b, _c, _d, _e, _f;
|
|
14
|
+
const { authToken, baseUrl } = authParams;
|
|
15
|
+
if (!authToken || !baseUrl) {
|
|
16
|
+
return {
|
|
17
|
+
success: false,
|
|
18
|
+
error: "Missing Okta OAuth token (authToken) or base URL (baseUrl) in authParams.",
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const requestConfig = {
|
|
23
|
+
headers: {
|
|
24
|
+
Authorization: `Bearer ${authToken}`,
|
|
25
|
+
Accept: "application/json",
|
|
26
|
+
"Content-Type": "application/json",
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
const endpointUrl = new URL(`/api/v1/groups/${params.groupId}`, baseUrl).toString();
|
|
30
|
+
const response = yield axiosClient.get(endpointUrl, requestConfig);
|
|
31
|
+
if (response.status === 200 && response.data) {
|
|
32
|
+
const group = {
|
|
33
|
+
id: response.data.id,
|
|
34
|
+
created: response.data.created,
|
|
35
|
+
lastUpdated: response.data.lastUpdated,
|
|
36
|
+
lastMembershipUpdated: response.data.lastMembershipUpdated,
|
|
37
|
+
objectClass: response.data.objectClass,
|
|
38
|
+
type: response.data.type,
|
|
39
|
+
profile: {
|
|
40
|
+
name: response.data.profile.name,
|
|
41
|
+
description: response.data.profile.description,
|
|
42
|
+
},
|
|
43
|
+
_links: {
|
|
44
|
+
logo: ((_b = response.data._links) === null || _b === void 0 ? void 0 : _b.logo) || [],
|
|
45
|
+
users: ((_c = response.data._links) === null || _c === void 0 ? void 0 : _c.users) || {},
|
|
46
|
+
apps: ((_d = response.data._links) === null || _d === void 0 ? void 0 : _d.apps) || {},
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
return { success: true, group };
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
const errorDetail = ((_e = response.data) === null || _e === void 0 ? void 0 : _e.errorSummary) || ((_f = response.data) === null || _f === void 0 ? void 0 : _f.message) || `Okta API responded with status ${response.status}`;
|
|
53
|
+
return { success: false, error: `Failed to retrieve group details: ${errorDetail}` };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
console.error("Error retrieving group details:", error);
|
|
58
|
+
let errorMessage = "Unknown error while retrieving group details";
|
|
59
|
+
if (error instanceof AxiosError && error.response) {
|
|
60
|
+
const oktaError = error.response.data;
|
|
61
|
+
errorMessage =
|
|
62
|
+
(oktaError === null || oktaError === void 0 ? void 0 : oktaError.errorSummary) || (oktaError === null || oktaError === void 0 ? void 0 : oktaError.message) || `Okta API request failed with status ${error.response.status}`;
|
|
63
|
+
}
|
|
64
|
+
else if (error instanceof Error) {
|
|
65
|
+
errorMessage = error.message;
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
success: false,
|
|
69
|
+
error: errorMessage,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
export default getOktaGroup;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { axiosClient } from "../../util/axiosClient.js";
|
|
11
|
+
const getOktaUser = (_a) => __awaiter(void 0, [_a], void 0, function* ({ authParams, params, }) {
|
|
12
|
+
const { authToken, baseUrl } = authParams;
|
|
13
|
+
if (!authToken || !baseUrl) {
|
|
14
|
+
return {
|
|
15
|
+
success: false,
|
|
16
|
+
error: "Missing Okta OAuth token (authToken) or base URL (baseUrl) in authParams.",
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const requestConfig = {
|
|
21
|
+
headers: {
|
|
22
|
+
Authorization: `Bearer ${authToken}`,
|
|
23
|
+
Accept: "application/json",
|
|
24
|
+
"Content-Type": "application/json",
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
const endpointUrl = new URL(`/api/v1/users/${params.userId}`, baseUrl).toString();
|
|
28
|
+
const response = yield axiosClient.get(endpointUrl, requestConfig);
|
|
29
|
+
if (response.status === 200 && response.data) {
|
|
30
|
+
return { success: true, user: response.data };
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
return { success: false, error: "Failed to retrieve user details" };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
return { success: false, error: error instanceof Error ? error.message : "Unknown error occurred" };
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
export default getOktaUser;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { AxiosError } from "axios";
|
|
11
|
+
import { axiosClient } from "../../util/axiosClient.js";
|
|
12
|
+
const listMFA = (_a) => __awaiter(void 0, [_a], void 0, function* ({ authParams, params, }) {
|
|
13
|
+
var _b, _c;
|
|
14
|
+
const { authToken, baseUrl } = authParams;
|
|
15
|
+
if (!authToken || !baseUrl) {
|
|
16
|
+
return {
|
|
17
|
+
success: false,
|
|
18
|
+
error: "Missing Okta OAuth token (authToken) or base URL (baseUrl) in authParams.",
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const endpointUrl = new URL(`/api/v1/users/${params.userId}/factors`, baseUrl).toString();
|
|
23
|
+
const requestConfig = {
|
|
24
|
+
headers: {
|
|
25
|
+
Authorization: `Bearer ${authToken}`,
|
|
26
|
+
Accept: "application/json",
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
const response = yield axiosClient.get(endpointUrl, requestConfig);
|
|
30
|
+
if (response.status === 200) {
|
|
31
|
+
return { success: true, factors: response.data };
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
const errorDetail = ((_b = response.data) === null || _b === void 0 ? void 0 : _b.errorSummary) || ((_c = response.data) === null || _c === void 0 ? void 0 : _c.message) || `Okta API responded with status ${response.status}`;
|
|
35
|
+
return { success: false, error: `Failed to list MFA factors: ${errorDetail}` };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.error("Error listing MFA factors:", error);
|
|
40
|
+
let errorMessage = "Unknown error while listing MFA factors";
|
|
41
|
+
if (error instanceof AxiosError && error.response) {
|
|
42
|
+
const oktaError = error.response.data;
|
|
43
|
+
errorMessage =
|
|
44
|
+
(oktaError === null || oktaError === void 0 ? void 0 : oktaError.errorSummary) || (oktaError === null || oktaError === void 0 ? void 0 : oktaError.message) || `Okta API request failed with status ${error.response.status}`;
|
|
45
|
+
}
|
|
46
|
+
else if (error instanceof Error) {
|
|
47
|
+
errorMessage = error.message;
|
|
48
|
+
}
|
|
49
|
+
return { success: false, error: errorMessage };
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
export default listMFA;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { AxiosError } from "axios";
|
|
11
|
+
import { axiosClient } from "../../util/axiosClient.js";
|
|
12
|
+
// page limit recommended by Okta documentation
|
|
13
|
+
// https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/#tag/Group/operation/listGroupUsers
|
|
14
|
+
const DEFAULT_LIMIT = 200;
|
|
15
|
+
const listOktaGroupMembers = (_a) => __awaiter(void 0, [_a], void 0, function* ({ authParams, params, }) {
|
|
16
|
+
var _b, _c, _d, _e;
|
|
17
|
+
const { authToken, baseUrl } = authParams;
|
|
18
|
+
if (!authToken || !baseUrl) {
|
|
19
|
+
return {
|
|
20
|
+
success: false,
|
|
21
|
+
error: "Missing Okta OAuth token (authToken) or base URL (baseUrl) in authParams.",
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const requestConfig = {
|
|
26
|
+
headers: {
|
|
27
|
+
Authorization: `Bearer ${authToken}`,
|
|
28
|
+
Accept: "application/json",
|
|
29
|
+
"Content-Type": "application/json",
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
const endpointUrl = new URL(`/api/v1/groups/${params.groupId}/users`, baseUrl);
|
|
33
|
+
endpointUrl.searchParams.set("limit", Math.min(DEFAULT_LIMIT, params.maxResults || DEFAULT_LIMIT).toString());
|
|
34
|
+
let nextUrl = endpointUrl.toString();
|
|
35
|
+
let members = [];
|
|
36
|
+
let totalFetched = 0;
|
|
37
|
+
while (nextUrl) {
|
|
38
|
+
const remainingResults = params.maxResults ? params.maxResults - totalFetched : DEFAULT_LIMIT;
|
|
39
|
+
const adjustedUrl = new URL(nextUrl);
|
|
40
|
+
if (params.maxResults && remainingResults < DEFAULT_LIMIT) {
|
|
41
|
+
adjustedUrl.searchParams.set("limit", Math.min(DEFAULT_LIMIT, remainingResults).toString());
|
|
42
|
+
}
|
|
43
|
+
console.debug("Fetching group members from URL:", adjustedUrl.toString());
|
|
44
|
+
const response = yield axiosClient.get(adjustedUrl.toString(), requestConfig);
|
|
45
|
+
if (response.status === 200 && Array.isArray(response.data)) {
|
|
46
|
+
members = members.concat(response.data.map(user => ({
|
|
47
|
+
id: user.id,
|
|
48
|
+
status: user.status,
|
|
49
|
+
created: user.created,
|
|
50
|
+
activated: user.activated,
|
|
51
|
+
statusChanged: user.statusChanged,
|
|
52
|
+
lastLogin: user.lastLogin,
|
|
53
|
+
lastUpdated: user.lastUpdated,
|
|
54
|
+
passwordChanged: user.passwordChanged,
|
|
55
|
+
type: user.type,
|
|
56
|
+
profile: {
|
|
57
|
+
firstName: user.profile.firstName,
|
|
58
|
+
lastName: user.profile.lastName,
|
|
59
|
+
mobilePhone: user.profile.mobilePhone,
|
|
60
|
+
secondEmail: user.profile.secondEmail,
|
|
61
|
+
login: user.profile.login,
|
|
62
|
+
email: user.profile.email,
|
|
63
|
+
},
|
|
64
|
+
})));
|
|
65
|
+
totalFetched += response.data.length;
|
|
66
|
+
if (params.maxResults && totalFetched >= params.maxResults) {
|
|
67
|
+
members = members.slice(0, params.maxResults);
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
nextUrl = ((_c = (_b = response.headers.link) === null || _b === void 0 ? void 0 : _b.match(/<([^>]+)>;\s*rel="next"/)) === null || _c === void 0 ? void 0 : _c[1]) || null;
|
|
71
|
+
if (nextUrl) {
|
|
72
|
+
console.debug("Next page URL:", nextUrl);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const errorDetail = ((_d = response.data) === null || _d === void 0 ? void 0 : _d.errorSummary) || ((_e = response.data) === null || _e === void 0 ? void 0 : _e.message) || `Okta API responded with status ${response.status}`;
|
|
77
|
+
return { success: false, error: `Failed to list group members: ${errorDetail}` };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return { success: true, members };
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
console.error("Error listing group members:", error);
|
|
84
|
+
let errorMessage = "Unknown error while listing group members";
|
|
85
|
+
if (error instanceof AxiosError && error.response) {
|
|
86
|
+
const oktaError = error.response.data;
|
|
87
|
+
errorMessage =
|
|
88
|
+
(oktaError === null || oktaError === void 0 ? void 0 : oktaError.errorSummary) || (oktaError === null || oktaError === void 0 ? void 0 : oktaError.message) || `Okta API request failed with status ${error.response.status}`;
|
|
89
|
+
}
|
|
90
|
+
else if (error instanceof Error) {
|
|
91
|
+
errorMessage = error.message;
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
success: false,
|
|
95
|
+
error: errorMessage,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
export default listOktaGroupMembers;
|