UNPKG

@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
"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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