@microsoft/teams.apps
Version:
<p> <a href="https://www.npmjs.com/package/@microsoft/teams.apps" target="_blank"> <img src="https://img.shields.io/npm/v/@microsoft/teams.apps/latest" /> </a> <a href="https://www.npmjs.com/package/@microsoft/teams.apps?activeTab=code
424 lines • 38.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.isUnderServiceUrl = isUnderServiceUrl;
exports.isGraphRouteOpen = isGraphRouteOpen;
exports.openFileStream = openFileStream;
exports.collectStream = collectStream;
const stream_1 = require("stream");
const errors_1 = require("./errors");
const graph_share_1 = require("./graph-share");
/** How much of an error body to keep. Enough for a Graph error envelope, small enough to never matter. */
const ERROR_BODY_LIMIT = 2048;
/**
* Pull the human-readable part out of a Graph error envelope, falling back to the raw text.
*
* Graph replies `{ error: { code, message } }`, but a 401 can also come from the edge as HTML, so this must not assume JSON. Truncated because it lands in an exception message.
*/
function extractServiceError(raw) {
if (!raw) {
return undefined;
}
try {
const parsed = JSON.parse(raw);
const error = parsed?.error;
if (error?.code || error?.message) {
return truncate([error.code, error.message].filter(Boolean).join(': '));
}
}
catch {
// Not JSON. The raw text is still better than nothing.
}
return truncate(raw);
}
/** The `error.code` from a JSON error envelope, or `undefined` when the body is not one. */
function extractServiceErrorCode(raw) {
if (!raw) {
return undefined;
}
try {
const code = JSON.parse(raw)?.error?.code;
return typeof code === 'string' && code.trim() ? code.trim() : undefined;
}
catch {
return undefined;
}
}
function truncate(text) {
const collapsed = text.replace(/\s+/g, ' ').trim();
if (!collapsed) {
return undefined;
}
return collapsed.length > ERROR_BODY_LIMIT ? `${collapsed.slice(0, ERROR_BODY_LIMIT)}...` : collapsed;
}
/**
* Read at most one byte past {@link ERROR_BODY_LIMIT} from an error body and decode it as UTF-8.
*
* The bound has to be applied while reading rather than to the result. Appending a whole chunk and checking its size afterwards lets a single large chunk decide the buffer, and the host returning the error is not one the SDK controls. The one byte past the limit is what leaves {@link truncate} able to tell a body that merely reached the limit from one that ran past it.
*/
async function readBoundedUtf8(chunks) {
const kept = [];
let size = 0;
for await (const chunk of chunks) {
const remaining = ERROR_BODY_LIMIT + 1 - size;
kept.push(chunk.length > remaining ? chunk.subarray(0, remaining) : chunk);
size += Math.min(chunk.length, remaining);
if (size > ERROR_BODY_LIMIT) {
break;
}
}
return Buffer.concat(kept).toString('utf8');
}
/** Iterate a web stream so {@link readBoundedUtf8} can stop early, which `Response.text()` gives no opportunity to do. */
async function* streamChunks(stream) {
const reader = stream.getReader();
try {
for (;;) {
const { done, value } = await reader.read();
if (done) {
return;
}
if (value) {
yield value;
}
}
}
finally {
reader.releaseLock();
}
}
/**
* Whether `url` is under `serviceUrl`: both https, the same origin, and a path starting with the service URL's path, compared case-sensitively.
*
* Both are parsed first, so dot segments are resolved before the comparison and cannot climb out of the service URL's path.
*/
function isUnderServiceUrl(url, serviceUrl) {
if (!serviceUrl) {
return false;
}
let target;
let base;
try {
target = new URL(url);
base = new URL(serviceUrl);
}
catch {
return false;
}
if (base.protocol !== 'https:' || target.origin !== base.origin) {
return false;
}
// Paths are case-sensitive, so only the hex digits of percent escapes are normalized, since `%2f` and `%2F` are the same escape.
const normalizeEscapes = (path) => path.replace(/%[0-9a-f]{2}/gi, (escape) => escape.toUpperCase());
// Normalizes to exactly one trailing `/` so a path such as `/amer` cannot match `/americas`.
const basePath = `${base.pathname.replace(/\/+$/, '')}/`;
return normalizeEscapes(target.pathname).startsWith(normalizeEscapes(basePath));
}
/**
* Whether a file that arrived with only a `contentUrl` may be read through Graph in this scope, as this actor.
*
* Shared by the attachment mapper and the download dispatcher so the two cannot drift: a file `list()` surfaces on its `contentUrl` is always one `download()` will open. `personal` is open to every actor. `groupChat` and `channel` are open only to an agentic user, the only identity the platform delivers files to there at this time. Any other scope stays closed.
*/
function isGraphRouteOpen(scope, actor) {
if (scope === 'personal') {
return true;
}
return actor === 'agenticUser' && (scope === 'groupChat' || scope === 'channel');
}
/**
* Open a byte stream for an inbound file, keyed on its conversation scope so every scope's receive path extends this one place rather than branching in callers.
*
* `personal` takes either route. In `groupChat` and `channel` the platform delivers file attachments only to an agentic user, each carrying just a `contentUrl`, so they open only through Graph, as {@link isGraphRouteOpen} decides. Everything else throws {@link FileScopeNotSupportedError}.
*/
async function openFileStream(target, options) {
// Inline images live on the Bot API, so neither file route nor its scope restriction applies.
if (target.attachmentStyle === 'inlineImage') {
return openInlineImageStream(target, options);
}
if (target.scope === 'personal') {
return openPersonalFileStream(target, options);
}
// A `downloadUrl` outside `personal` keeps the scope error: that route is unvalidated there, and Graph is never substituted for a URL the platform supplied.
if (!target.downloadUrl && target.contentUrl && isGraphRouteOpen(target.scope, options?.credential?.actor)) {
return openGraphFileStream(target, target.contentUrl, options);
}
throw new errors_1.FileScopeNotSupportedError(target.scope);
}
async function openPersonalFileStream(target, options) {
const url = target.downloadUrl;
// The Agentic User case: a browsable `contentUrl` arrives in place of a `downloadUrl`, so Graph is the only route to the bytes.
if (!url) {
if (!target.contentUrl) {
throw new Error('cannot download personal file: no download URL is available');
}
return openGraphFileStream(target, target.contentUrl, options);
}
// Unreachable today: the platform's agentic path never sets a download URL, which is the only reason the arm above always fires for an agent. If that ever changes, the bytes still arrive and nothing fails, but they are fetched unauthenticated and attributed to nobody, so the switch away from the agent's own identity would be invisible except in an audit log. Warn rather than reroute: rerouting would ignore a working URL, which is the escalation the automatic-credential rule exists to prevent.
if (options?.credential?.actor === 'agenticUser') {
options.log?.warn('an agentic turn received a file carrying a pre-authorized download URL, which the platform did not previously send to agents. The SDK is using that URL, so these bytes are not attributed to the agentic user.');
}
if (!/^https:\/\//i.test(url)) {
throw new Error('cannot download file: download URL must use https');
}
const response = await requestFile(url, options);
if (response.status === 401 || response.status === 403) {
response.discard();
// Terminal. The URL carried its own credential and that credential has lapsed, and the SDK does not perform a fallback via app identity or user-delegated permissions on the developer's behalf. The file has to be sent again.
throw new errors_1.FileUrlExpiredError(options?.priorFetchSucceeded ? 'reread' : 'firstFetch');
}
if (!response.ok || !response.stream) {
response.discard();
throw new Error(`failed to download file: ${response.status} ${response.statusText}`.trim());
}
const contentType = response.contentType ?? target.contentType ?? 'application/octet-stream';
return { stream: response.stream, sourceUrl: url, contentType };
}
/**
* Fetch bytes by resolving the item through Graph's `/shares` endpoint.
*
* Unlike the pre-authorized path, which strips `Authorization`, this is an ordinary authenticated Graph call and fails without a bearer token.
*/
async function openGraphFileStream(target, sharingUrl, options) {
const actor = options?.credential?.actor;
const { token, failure } = await tryResolveToken(options);
// Detectable before any HTTP call, so a missing consent names itself instead of arriving as an opaque Graph 401.
if (!token || carriesNoGraphPermissions(token)) {
// An acquisition that threw is not the same as an identity with no permissions, and the guidance for one is wrong for the other, so the cause is carried rather than dropped.
throw new errors_1.FileCredentialError(actor, failure);
}
const url = (0, graph_share_1.buildDriveItemContentUrl)(sharingUrl, options?.credential?.baseUrlRoot);
options?.log?.debug(`resolving bytes through Graph /shares as '${actor}'`);
const response = await requestFile(url, options, token);
if (response.status === 401 || response.status === 403) {
// The status is carried rather than collapsed: a 401 means the token itself was rejected and a 403 means the identity lacks the grant, and those have different remedies. Within a 403, an unconsented scope and a file never shared are still indistinguishable, so the service's own text is carried too.
throw new errors_1.FileAccessError(response.status, actor, extractServiceError(await response.readText()));
}
if (!response.ok || !response.stream) {
// Carry Graph's own text and name the identity, as the 401/403 arm does.
// An unexpected status here is often diagnosable only from the service message, so discarding it would leave the caller a bare status code.
const details = extractServiceError(await response.readText());
throw new Error(`failed to download file through Graph as '${actor ?? 'app'}': ${response.status} ${response.statusText}`.trim() +
(details ? ` (${details})` : ''));
}
const contentType = response.contentType ?? target.contentType ?? 'application/octet-stream';
return { stream: response.stream, sourceUrl: url, contentType };
}
/**
* Fetch an inline image's bytes from the Bot API with the turn's Bot API token.
*
* The token is sent only to a URL under {@link BotApiCredential.serviceUrl}.
*/
async function openInlineImageStream(target, options) {
const url = target.contentUrl;
if (!url) {
throw new Error('cannot download inline image: no content URL is available');
}
if (!/^https:\/\//i.test(url)) {
throw new Error('cannot download inline image: content URL must use https');
}
const credential = options?.botApiCredential;
if (!credential) {
throw new Error('cannot download inline image: no Bot API token is available');
}
if (!isUnderServiceUrl(url, credential.serviceUrl)) {
throw new Error('cannot download inline image: content URL is not under the activity\'s service URL');
}
const actor = credential.actor;
const token = await credential.token();
if (!token) {
throw new Error('cannot download inline image: no Bot API token is available');
}
options?.log?.debug(`reading inline image bytes from the Bot API as '${actor}'`);
const response = await requestFile(url, options, token);
if (response.status === 415) {
const body = await response.readText();
throw new errors_1.InlineImageTransformationError(response.status, actor, extractServiceError(body), extractServiceErrorCode(body));
}
if (response.status === 401 || response.status === 403) {
throw new errors_1.InlineImageAccessError(response.status, actor, extractServiceError(await response.readText()));
}
if (!response.ok || !response.stream) {
const details = extractServiceError(await response.readText());
throw new Error(`failed to download inline image as '${actor}': ${response.status} ${response.statusText}`.trim() +
(details ? ` (${details})` : ''));
}
const contentType = response.contentType ?? target.contentType ?? 'application/octet-stream';
return { stream: response.stream, sourceUrl: url, contentType };
}
/**
* Issue the byte request.
*
* `bearer` is a parameter rather than downloader configuration because the two paths need opposite behaviour: the pre-authorized URL must send no `Authorization`, Graph requires one. Hoisting auth into shared setup breaks the first case, which is the dangerous direction: it leaks a bot token to third-party storage for every bot working today, whereas forgetting it on the Graph path fails immediately and only there.
*/
/**
* Resolve a Graph token without throwing.
*
* On the expiry path the caller already holds a more precise error, so an acquisition failure must leave it intact rather than surfacing as an untyped rejection.
*/
async function tryResolveToken(options) {
if (!options?.credential) {
return {};
}
try {
return { token: await options.credential.token() };
}
catch (err) {
const failure = err.message;
options.log?.debug(`could not acquire a Graph token: ${failure}`);
return { failure };
}
}
/**
* The permissions a token carries, as a flat list, or `undefined` when the token is not a decodable JWT.
*
* An app-only token lists application permissions in `roles`; a delegated or agentic-user token lists scopes in `scp`, space-delimited and absent entirely when there are none. `undefined` and `[]` mean different things and both callers depend on the difference: undecodable is "cannot tell", empty is "decoded, and there is nothing there".
*/
function permissionsOf(token) {
try {
const payload = token.split('.')[1];
if (!payload) {
return undefined;
}
const claims = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
const roles = Array.isArray(claims.roles) ? claims.roles.filter((r) => typeof r === 'string') : [];
const scopes = typeof claims.scp === 'string' ? claims.scp.trim().split(/\s+/).filter(Boolean) : [];
return [...roles, ...scopes];
}
catch {
return undefined;
}
}
/**
* Detect a credential that cannot reach a drive item, before spending a request to find out.
*
* Real Graph answers such a token with a `401 generalException` or a `403`, indistinguishable on the wire from a genuine denial but fixed in the identity's consented permissions rather than in file sharing.
*
* The predicate is "carries no file-capable permission", not "carries none at all". Emptiness was sufficient while the app rung was the concern, because an app registration with no Graph permissions really does return `roles: []`. It is not sufficient for an Agentic User under `.default`, where a blueprint consented to unrelated scopes such as `Mail.Send` returns a populated `scp` that passes an emptiness check and then fails late as an ambiguous 403.
*
* Any `Files.*` or `Sites.*` permission is admitted, which is deliberately generous. `Sites.Selected` is a known false positive: it begins with `Sites.` but grants nothing until an admin allowlists specific sites. A false positive here degrades to the previous behaviour of calling Graph and reporting what it says, which is the safe direction to be wrong in.
*
* Fails open on an undecodable token, which proceeds to the call rather than blocking a fetch that might have worked.
*/
function carriesNoGraphPermissions(token) {
const permissions = permissionsOf(token);
if (permissions === undefined) {
return false;
}
return !permissions.some((p) => /^(files|sites)\./i.test(p));
}
async function requestFile(url, options, bearer) {
if (options?.fetch) {
return fromFetchResponse(await options.fetch(url, {
signal: options.signal,
...(bearer ? { headers: { Authorization: `Bearer ${bearer}` } } : {}),
}));
}
if (options?.httpClient) {
return requestViaHttpClient(url, options.httpClient, options.signal, bearer);
}
throw new Error('cannot download file: no HTTP client is available');
}
async function requestViaHttpClient(url, client, signal, bearer) {
const originalHost = new URL(url).host.toLowerCase();
const response = await client.get(url, {
responseType: 'stream',
signal,
// No bearer means the pre-authorized path, whose URL carries its own credential and can be rejected if a bot token rides along. `token` only suppresses the token-derived header, so a client-configured default `Authorization` must be overridden too, or `withConfig` merges it in and sends it to third-party storage.
token: () => bearer,
headers: { Authorization: bearer ? `Bearer ${bearer}` : undefined },
// We map 401/403 onto typed errors ourselves, so keep axios from throwing first.
validateStatus: () => true,
// Storage answers with a 302 to the host actually holding the bytes, so redirects have to be followed. They must not be followed *down* to plaintext: an `https` to `http` hop would put the file's bytes on the wire in the clear.
beforeRedirect: (redirect) => {
if (redirect.protocol !== 'https:') {
throw new Error(`cannot download file: a redirect destination must use https, got "${redirect.protocol}//${redirect.host ?? redirect.hostname}". The file's bytes would cross that hop in the clear.`);
}
// The redirect follower keeps `Authorization` on a hop to a subdomain, so the token is dropped here whenever the host changes.
if (String(redirect.host ?? redirect.hostname ?? '').toLowerCase() !== originalHost) {
for (const name of Object.keys(redirect.headers ?? {})) {
if (name.toLowerCase() === 'authorization') {
delete redirect.headers[name];
}
}
}
},
});
const body = response.data;
const contentTypeHeader = response.headers?.['content-type'];
return {
status: response.status,
statusText: response.statusText ?? '',
ok: response.status >= 200 && response.status < 300,
contentType: typeof contentTypeHeader === 'string' ? contentTypeHeader : undefined,
// `packages/apps` is Node-only, so adapting Node's `Readable` to a web `ReadableStream` is safe here.
stream: body ? stream_1.Readable.toWeb(body) : undefined,
discard: () => body?.destroy(),
readText: async () => {
if (!body) {
return undefined;
}
try {
const text = await readBoundedUtf8(body);
body.destroy();
return truncate(text);
}
catch {
body.destroy();
return undefined;
}
},
};
}
function fromFetchResponse(response) {
return {
status: response.status,
statusText: response.statusText,
ok: response.ok,
contentType: response.headers.get('content-type') ?? undefined,
stream: response.body ?? undefined,
discard: () => {
void response.body?.cancel().catch(() => { });
},
readText: async () => {
if (!response.body) {
return undefined;
}
try {
return truncate(await readBoundedUtf8(streamChunks(response.body)));
}
catch {
return undefined;
}
},
};
}
/**
* Read a byte stream to completion into a single `Uint8Array`.
*/
async function collectStream(stream) {
const reader = stream.getReader();
const chunks = [];
let total = 0;
try {
for (;;) {
const { done, value } = await reader.read();
if (done) {
break;
}
if (value) {
total += value.byteLength;
chunks.push(value);
}
}
}
finally {
reader.releaseLock();
}
const out = new Uint8Array(total);
let offset = 0;
for (const chunk of chunks) {
out.set(chunk, offset);
offset += chunk.byteLength;
}
return out;
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZG93bmxvYWQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvZmlsZXMvZG93bmxvYWQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFzTEEsOENBeUJDO0FBeURELDRDQU1DO0FBT0Qsd0NBbUJDO0FBdVVELHNDQStCQztBQTlvQkQsbUNBQWtDO0FBS2xDLHFDQVFrQjtBQUNsQiwrQ0FBeUQ7QUFHekQsMEdBQTBHO0FBQzFHLE1BQU0sZ0JBQWdCLEdBQUcsSUFBSSxDQUFDO0FBRTlCOzs7O0dBSUc7QUFDSCxTQUFTLG1CQUFtQixDQUFDLEdBQVk7SUFDdkMsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBQ1QsT0FBTyxTQUFTLENBQUM7SUFDbkIsQ0FBQztJQUVELElBQUksQ0FBQztRQUNILE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDL0IsTUFBTSxLQUFLLEdBQUcsTUFBTSxFQUFFLEtBQUssQ0FBQztRQUU1QixJQUFJLEtBQUssRUFBRSxJQUFJLElBQUksS0FBSyxFQUFFLE9BQU8sRUFBRSxDQUFDO1lBQ2xDLE9BQU8sUUFBUSxDQUFDLENBQUMsS0FBSyxDQUFDLElBQUksRUFBRSxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO1FBQzFFLENBQUM7SUFDSCxDQUFDO0lBQUMsTUFBTSxDQUFDO1FBQ1AsdURBQXVEO0lBQ3pELENBQUM7SUFFRCxPQUFPLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQztBQUN2QixDQUFDO0FBRUQsNEZBQTRGO0FBQzVGLFNBQVMsdUJBQXVCLENBQUMsR0FBWTtJQUMzQyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7UUFDVCxPQUFPLFNBQVMsQ0FBQztJQUNuQixDQUFDO0lBRUQsSUFBSSxDQUFDO1FBQ0gsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFD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