@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
189 lines • 14.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpServer = void 0;
const teams_common_1 = require("@microsoft/teams.common");
const inbound_activity_token_validator_1 = require("../middleware/auth/inbound-activity-token-validator");
/**
* Sanitize an attacker-controlled activity field for inclusion in a log line.
* Strips control characters (CR/LF/tab/etc.) and caps length to defeat log injection.
*/
function safeLogField(v) {
// eslint-disable-next-line no-control-regex
return String(v ?? 'unknown').replace(/[\r\n\t\x00-\x1f\x7f]/g, '').slice(0, 64);
}
/**
* Configurable HTTP server for receiving Teams activities
*/
class HttpServer {
/**
* Callback invoked when a valid activity request arrives
* App should set this to process activities
*/
onRequest;
logger;
credentials;
cloud;
initialized = false;
serviceTokenValidator;
dangerouslyAllowUnauthenticatedRequests;
_adapter;
_messagingEndpoint;
/**
* Get the underlying adapter
* Useful for plugins that need adapter-specific features
*/
get adapter() {
return this._adapter;
}
/**
* Get the messaging endpoint path
*/
get messagingEndpoint() {
return this._messagingEndpoint;
}
constructor(adapter, options) {
this._adapter = adapter;
this.logger = options.logger ?? new teams_common_1.ConsoleLogger('HttpServer');
this._messagingEndpoint = options.messagingEndpoint;
this.dangerouslyAllowUnauthenticatedRequests = options.dangerouslyAllowUnauthenticatedRequests
?? false;
}
/**
* Initialize the server with dependencies and register routes.
* Can be called multiple times - only initializes once
* Called by App.initialize()
*/
async initialize(deps) {
if (this.initialized) {
this.logger.debug('HttpServer already initialized, skipping');
return;
}
this.credentials = deps.credentials;
this.cloud = deps.cloud;
if (!this.credentials && !this.dangerouslyAllowUnauthenticatedRequests) {
this.logger.warn('No credentials configured and dangerouslyAllowUnauthenticatedRequests is not enabled. ' +
'All incoming requests will be rejected. Configure client authentication to securely receive messages, ' +
'or set dangerouslyAllowUnauthenticatedRequests: true for local development.');
}
// Initialize service token validator if credentials provided and auth validation is enabled
if (this.credentials && !this.dangerouslyAllowUnauthenticatedRequests) {
this.serviceTokenValidator = new inbound_activity_token_validator_1.InboundActivityTokenValidator(this.credentials.clientId, this.credentials.tenantId, undefined, // serviceUrl will be validated from activity body
this.logger, deps.cloud);
}
else if (!this.credentials && this.dangerouslyAllowUnauthenticatedRequests) {
this.logger.warn('No credentials configured (CLIENT_ID / CLIENT_SECRET / TENANT_ID), ' +
'but dangerouslyAllowUnauthenticatedRequests is enabled. ' +
`Bot will accept unauthenticated requests on ${this._messagingEndpoint}.`);
}
else if (this.credentials && this.dangerouslyAllowUnauthenticatedRequests) {
this.logger.warn('Credentials are configured, but dangerouslyAllowUnauthenticatedRequests is enabled. ' +
`Bot will bypass Teams service token validation on ${this._messagingEndpoint}.`);
}
// Register Teams bot endpoint (POST only)
this._adapter.registerRoute('POST', this._messagingEndpoint, async (request) => {
return this.handleRequest(request);
});
this.initialized = true;
}
/**
* Start the HTTP server
* Called by App.start()
*/
async start(port) {
if (!this._adapter.start) {
throw new Error('Adapter does not implement start(). ' +
'Either implement start() in your adapter, or manage server lifecycle manually.');
}
await this._adapter.start(port);
}
/**
* Stop the HTTP server
* Called by App.stop() if implemented
*/
async stop() {
if (!this._adapter.stop) {
this.logger.warn('Adapter does not implement stop(). Skipping server shutdown.');
return;
}
await this._adapter.stop();
}
/**
* Register a route handler with the adapter
* Used by app.function() and other app methods
*/
registerRoute(method, path, handler) {
this._adapter.registerRoute(method, path, handler);
}
/**
* Serve static files from a directory
* Used by app.tab() and other app methods
*/
serveStatic(path, directory) {
if (this._adapter.serveStatic) {
this._adapter.serveStatic(path, directory);
}
}
/**
* Handle incoming activity request
* Validates the inbound JWT, dispatches to the app, and returns the response.
*/
async handleRequest(request) {
try {
const body = request.body;
this.logger.debug('Handling activity', body);
const auth = await this.authorize(request.headers, body);
if (!auth.success) {
return { status: 401, body: { error: auth.error } };
}
if (!this.onRequest) {
throw new Error('HttpServer.onRequest callback not set');
}
const response = await this.onRequest({ body, token: auth.token });
return { status: response.status || 200, body: response.body };
}
catch (err) {
this.logger.error('Error processing activity:', err);
return { status: 500, body: { error: 'Internal server error' } };
}
}
/**
* Authorize the request by validating the JWT token.
*/
async authorize(headers, body) {
if (this.dangerouslyAllowUnauthenticatedRequests) {
const serviceUrl = body.serviceUrl || '';
return {
success: true,
token: {
appId: '',
from: 'azure',
fromId: '',
serviceUrl,
isExpired: () => false,
},
};
}
if (!this.credentials) {
return { success: false, error: 'Authentication not configured' };
}
const raw = headers['authorization'];
const authHeader = Array.isArray(raw) ? raw[0] : raw;
if (!authHeader) {
this.logger.warn(`inbound activity rejected (type=${safeLogField(body?.type)}, id=${safeLogField(body?.id)}): missing Authorization header (responding 401)`);
return { success: false, error: 'Missing authorization header' };
}
if (!this.serviceTokenValidator) {
throw new Error('Service token validator not initialized - credentials required');
}
try {
const token = await this.serviceTokenValidator.check(authHeader, body);
return { success: true, token };
}
catch (err) {
this.logger.error('JWT validation failed', err);
return { success: false, error: 'JWT validation failed' };
}
}
}
exports.HttpServer = HttpServer;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaHR0cC1zZXJ2ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaHR0cC9odHRwLXNlcnZlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFPQSwwREFBaUU7QUFHakUsMEdBQW9HO0FBUXBHOzs7R0FHRztBQUNILFNBQVMsWUFBWSxDQUFDLENBQVU7SUFDOUIsNENBQTRDO0lBQzVDLE9BQU8sTUFBTSxDQUFDLENBQUMsSUFBSSxTQUFTLENBQUMsQ0FBQyxPQUFPLENBQUMsd0JBQXdCLEVBQUUsRUFBRSxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQztBQUNuRixDQUFDO0FBdUJEOztHQUVHO0FBQ0gsTUFBYSxVQUFVO0lBQ3JCOzs7T0FHRztJQUNILFNBQVMsQ0FBc0Q7SUFFckQsTUFBTSxDQUFVO0lBQ2hCLFdBQVcsQ0FBZTtJQUMxQixLQUFLLENBQW9CO0lBQ3pCLFdBQVcsR0FBWSxLQUFLLENBQUM7SUFDN0IscUJBQXFCLENBQWlDO0lBRXhELHVDQUF1QyxDQUFVO0lBQ2pELFFBQVEsQ0FBcUI7SUFDN0Isa0JBQWtCLENBQVM7SUFFbkM7OztPQUdHO0lBQ0gsSUFBSSxPQUFPO1FBQ1QsT0FBTyxJQUFJLENBQUMsUUFBUSxDQUFDO0lBQ3ZCLENBQUM7SUFFRDs7T0FFRztJQUNILElBQUksaUJBQWlCO1FBQ25CLE9BQU8sSUFBSSxDQUFDLGtCQUFrQixDQUFDO0lBQ2pDLENBQUM7SUFFRCxZQUFZLE9BQTJCLEVBQUUsT0FBMEI7UUFDakUsSUFBSSxDQUFDLFFBQVEsR0FBRyxPQUFPLENBQUM7UUFDeEIsSUFBSSxDQUFDLE1BQU0sR0FBRyxPQUFPLENBQUMsTUFBTSxJQUFJLElBQUksNEJBQWEsQ0FBQyxZQUFZLENBQUMsQ0FBQztRQUNoRSxJQUFJLENBQUMsa0JBQWtCLEdBQUcsT0FBTyxDQUFDLGlCQUFpQixDQUFDO1FBQ3BELElBQUksQ0FBQyx1Q0FBdUMsR0FBRyxPQUFPLENBQUMs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