Skip to content

Fix: fix telemetry headers, add support for external bot auth #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
21 changes: 20 additions & 1 deletion packages/api/src/auth/credentials.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
export interface Credentials {
/**
* credentials for app authentication
*/
export type Credentials = ClientCredentials | TokenCredentials;

/**
* credentials for authentication
* of an app via `clientId` and `clientSecret`
*/
export interface ClientCredentials {
readonly clientId: string;
readonly clientSecret: string;
readonly tenantId?: string;
}

/**
* credentials for authentication
* of an app via any external auth method
*/
export interface TokenCredentials {
readonly clientId: string;
readonly tenantId?: string;
readonly token: (scope: string | string[], tenantId?: string) => string | Promise<string>;
}
2 changes: 1 addition & 1 deletion packages/api/src/auth/json-web-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export class JsonWebToken implements Token {

constructor(value: string) {
this._value = value;
this._payload = jwtDecode(value);
this._payload = jwtDecode(value, { header: true });
}

toString() {
Expand Down
40 changes: 30 additions & 10 deletions packages/api/src/clients/bot/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import { Client, ClientOptions } from '@microsoft/spark.common/http';

import { Credentials } from '../../auth';

export type GetBotTokenParams = Credentials;

export interface GetBotTokenResponse {
readonly token_type: 'Bearer';
readonly expires_in: number;
Expand All @@ -31,14 +29,25 @@ export class BotTokenClient {
}
}

async get(params: GetBotTokenParams) {
const tenantId = params.tenantId || 'botframework.com';
async get(credentials: Credentials) {
if ('token' in credentials) {
return {
token_type: 'Bearer',
expires_in: -1,
access_token: await credentials.token(
'https://api.botframework.com/.default',
credentials.tenantId
),
};
}

const tenantId = credentials.tenantId || 'botframework.com';
const res = await this.http.post<GetBotTokenResponse>(
`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`,
qs.stringify({
grant_type: 'client_credentials',
client_id: params.clientId,
client_secret: params.clientSecret,
client_id: credentials.clientId,
client_secret: credentials.clientSecret,
scope: 'https://api.botframework.com/.default',
}),
{
Expand All @@ -49,14 +58,25 @@ export class BotTokenClient {
return res.data;
}

async getGraph(params: GetBotTokenParams) {
const tenantId = params.tenantId || 'botframework.com';
async getGraph(credentials: Credentials) {
if ('token' in credentials) {
return {
token_type: 'Bearer',
expires_in: -1,
access_token: await credentials.token(
'https://graph.microsoft.com/.default',
credentials.tenantId
),
};
}

const tenantId = credentials.tenantId || 'botframework.com';
const res = await this.http.post<GetBotTokenResponse>(
`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`,
qs.stringify({
grant_type: 'client_credentials',
client_id: params.clientId,
client_secret: params.clientSecret,
client_id: credentials.clientId,
client_secret: credentials.clientSecret,
scope: 'https://graph.microsoft.com/.default',
}),
{
Expand Down
2 changes: 1 addition & 1 deletion packages/api/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"extends": "@microsoft/spark.config/tsconfig.node.json",
"extends": "@microsoft/spark.config/tsconfig.esm.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
Expand Down
18 changes: 12 additions & 6 deletions packages/apps/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ export class App {
protected startedAt?: Date;
protected port?: number;

private readonly _userAgent = `teams[apps]/${pkg.version}`;
private readonly _userAgent = `spark[apps]/${pkg.version}`;
private readonly _manifest: Partial<manifest.Manifest>;
private _tokens: AppTokens = {};

Expand Down Expand Up @@ -224,14 +224,19 @@ export class App {
);

const clientId = this.options.clientId || process.env.CLIENT_ID;
const clientSecret = this.options.clientSecret || process.env.CLIENT_SECRET;
const tenantId = this.options.tenantId || process.env.TENANT_ID;
const clientSecret =
('clientSecret' in this.options ? this.options.clientSecret : undefined) ||
process.env.CLIENT_SECRET;
const tenantId =
('tenantId' in this.options ? this.options.tenantId : undefined) || process.env.TENANT_ID;
const token = 'token' in this.options ? this.options.token : undefined;

if (clientId && clientSecret) {
this.credentials = {
clientId,
clientSecret,
tenantId,
token,
};
}

Expand All @@ -256,7 +261,9 @@ export class App {
* start the app
* @param port port to listen on
*/
async start(port = 3000) {
async start(port?: number | string) {
this.port = +(port || process.env.PORT || 3000);

try {
if (this.credentials) {
const botResponse = await this.api.bots.token.get(this.credentials);
Expand All @@ -269,12 +276,11 @@ export class App {

for (const plugin of this.plugins) {
if (plugin.onStart) {
await plugin.onStart(port);
await plugin.onStart(this.port);
}
}

this.events.emit('start', this.log);
this.port = port;
this.startedAt = new Date();
} catch (err: any) {
this.events.emit('error', { err, log: this.log });
Expand Down
7 changes: 5 additions & 2 deletions packages/apps/src/middleware/with-client-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ export type ClientAuthRequest = express.Request & {
context?: ClientContext;
};

export function withClientAuth({ logger, clientId, clientSecret, tenantId }: WithClientAuthParams) {
const log = logger;
export function withClientAuth(params: WithClientAuthParams) {
const log = params.logger;
const clientId = params.clientId;
const clientSecret = 'clientSecret' in params ? params.clientSecret : undefined;
const tenantId = 'tenantId' in params ? params.tenantId : undefined;

return (req: ClientAuthRequest, res: express.Response, next: express.NextFunction) => {
const appClientId = req.header('X-Spark-App-Client-Id');
Expand Down
16 changes: 12 additions & 4 deletions packages/botbuilder/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,22 @@ export class BotBuilderPlugin extends HttpPlugin {
super.onInit(app);

if (!this.adapter) {
const clientId = app.credentials?.clientId;
const clientSecret =
app.credentials && 'clientSecret' in app.credentials
? app.credentials?.clientSecret
: undefined;
const tenantId =
app.credentials && 'tenantId' in app.credentials ? app.credentials?.tenantId : undefined;

this.adapter = new CloudAdapter(
new ConfigurationBotFrameworkAuthentication(
{},
new ConfigurationServiceClientCredentialFactory({
MicrosoftAppType: app.credentials?.tenantId ? 'SingleTenant' : 'MultiTenant',
MicrosoftAppId: app.credentials?.clientId,
MicrosoftAppPassword: app.credentials?.clientSecret,
MicrosoftAppTenantId: app.credentials?.tenantId,
MicrosoftAppType: tenantId ? 'SingleTenant' : 'MultiTenant',
MicrosoftAppId: clientId,
MicrosoftAppPassword: clientSecret,
MicrosoftAppTenantId: tenantId,
})
)
);
Expand Down
5 changes: 3 additions & 2 deletions packages/client/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,9 @@ export class App {
'x-spark-app-id': this.context.app.id,
'x-spark-app-session-id': this.context.app.sessionId,
'x-spark-app-client-id': this.options.clientId,
'x-spark-app-client-secret': this.options.clientSecret,
'x-spark-app-tenant-id': this.options.tenantId,
'x-spark-app-client-secret':
'clientSecret' in this.options ? this.options.clientSecret : undefined,
'x-spark-app-tenant-id': 'tenantId' in this.options ? this.options.tenantId : undefined,
'x-spark-tenant-id': this.context.user?.tenant?.id,
'x-spark-user-id': this.context.user?.id,
'x-spark-team-id': this.context.team?.internalId,
Expand Down
6 changes: 3 additions & 3 deletions packages/graph/scripts/client.ts.template
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@ export class {{capitalize name}}Client {
baseUrl: 'https://graph.microsoft.com/v1.0',
headers: {
'Content-Type': 'application/json',
'User-Agent': `teams[graph]/${pkg.version}`
'User-Agent': `spark[graph]/${pkg.version}`
}
});
} else if ('request' in options) {
this.http = options.clone({
baseUrl: 'https://graph.microsoft.com/v1.0',
headers: {
'Content-Type': 'application/json',
'User-Agent': `teams[graph]/${pkg.version}`,
'User-Agent': `spark[graph]/${pkg.version}`,
}
});
} else {
Expand All @@ -78,7 +78,7 @@ export class {{capitalize name}}Client {
baseUrl: 'https://graph.microsoft.com/v1.0',
headers: {
'Content-Type': 'application/json',
'User-Agent': `teams[graph]/${pkg.version}`,
'User-Agent': `spark[graph]/${pkg.version}`,
...options.headers
}
});
Expand Down
6 changes: 3 additions & 3 deletions packages/graph/src/appCatalogs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ export class AppCatalogsClient {
baseUrl: 'https://graph.microsoft.com/v1.0',
headers: {
'Content-Type': 'application/json',
'User-Agent': `teams[graph]/${pkg.version}`,
'User-Agent': `spark[graph]/${pkg.version}`,
},
});
} else if ('request' in options) {
this.http = options.clone({
baseUrl: 'https://graph.microsoft.com/v1.0',
headers: {
'Content-Type': 'application/json',
'User-Agent': `teams[graph]/${pkg.version}`,
'User-Agent': `spark[graph]/${pkg.version}`,
},
});
} else {
Expand All @@ -59,7 +59,7 @@ export class AppCatalogsClient {
baseUrl: 'https://graph.microsoft.com/v1.0',
headers: {
'Content-Type': 'application/json',
'User-Agent': `teams[graph]/${pkg.version}`,
'User-Agent': `spark[graph]/${pkg.version}`,
...options.headers,
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@ export class BotClient {
baseUrl: 'https://graph.microsoft.com/v1.0',
headers: {
'Content-Type': 'application/json',
'User-Agent': `teams[graph]/${pkg.version}`,
'User-Agent': `spark[graph]/${pkg.version}`,
},
});
} else if ('request' in options) {
this.http = options.clone({
baseUrl: 'https://graph.microsoft.com/v1.0',
headers: {
'Content-Type': 'application/json',
'User-Agent': `teams[graph]/${pkg.version}`,
'User-Agent': `spark[graph]/${pkg.version}`,
},
});
} else {
Expand All @@ -62,7 +62,7 @@ export class BotClient {
baseUrl: 'https://graph.microsoft.com/v1.0',
headers: {
'Content-Type': 'application/json',
'User-Agent': `teams[graph]/${pkg.version}`,
'User-Agent': `spark[graph]/${pkg.version}`,
...options.headers,
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@ export class AppDefinitionsClient {
baseUrl: 'https://graph.microsoft.com/v1.0',
headers: {
'Content-Type': 'application/json',
'User-Agent': `teams[graph]/${pkg.version}`,
'User-Agent': `spark[graph]/${pkg.version}`,
},
});
} else if ('request' in options) {
this.http = options.clone({
baseUrl: 'https://graph.microsoft.com/v1.0',
headers: {
'Content-Type': 'application/json',
'User-Agent': `teams[graph]/${pkg.version}`,
'User-Agent': `spark[graph]/${pkg.version}`,
},
});
} else {
Expand All @@ -62,7 +62,7 @@ export class AppDefinitionsClient {
baseUrl: 'https://graph.microsoft.com/v1.0',
headers: {
'Content-Type': 'application/json',
'User-Agent': `teams[graph]/${pkg.version}`,
'User-Agent': `spark[graph]/${pkg.version}`,
...options.headers,
},
});
Expand Down
6 changes: 3 additions & 3 deletions packages/graph/src/appCatalogs/teamsApps/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ export class TeamsAppsClient {
baseUrl: 'https://graph.microsoft.com/v1.0',
headers: {
'Content-Type': 'application/json',
'User-Agent': `teams[graph]/${pkg.version}`,
'User-Agent': `spark[graph]/${pkg.version}`,
},
});
} else if ('request' in options) {
this.http = options.clone({
baseUrl: 'https://graph.microsoft.com/v1.0',
headers: {
'Content-Type': 'application/json',
'User-Agent': `teams[graph]/${pkg.version}`,
'User-Agent': `spark[graph]/${pkg.version}`,
},
});
} else {
Expand All @@ -59,7 +59,7 @@ export class TeamsAppsClient {
baseUrl: 'https://graph.microsoft.com/v1.0',
headers: {
'Content-Type': 'application/json',
'User-Agent': `teams[graph]/${pkg.version}`,
'User-Agent': `spark[graph]/${pkg.version}`,
...options.headers,
},
});
Expand Down
6 changes: 3 additions & 3 deletions packages/graph/src/appRoleAssignments/checkMemberGroups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,15 @@ export class CheckMemberGroupsClient {
baseUrl: 'https://graph.microsoft.com/v1.0',
headers: {
'Content-Type': 'application/json',
'User-Agent': `teams[graph]/${pkg.version}`,
'User-Agent': `spark[graph]/${pkg.version}`,
},
});
} else if ('request' in options) {
this.http = options.clone({
baseUrl: 'https://graph.microsoft.com/v1.0',
headers: {
'Content-Type': 'application/json',
'User-Agent': `teams[graph]/${pkg.version}`,
'User-Agent': `spark[graph]/${pkg.version}`,
},
});
} else {
Expand All @@ -61,7 +61,7 @@ export class CheckMemberGroupsClient {
baseUrl: 'https://graph.microsoft.com/v1.0',
headers: {
'Content-Type': 'application/json',
'User-Agent': `teams[graph]/${pkg.version}`,
'User-Agent': `spark[graph]/${pkg.version}`,
...options.headers,
},
});
Expand Down
Loading