fix #1155 by renaming signalRTokenHeader to access_token (#1343) (#1359)

This commit is contained in:
Andrew Stanton-Nurse 2018-01-28 14:52:28 -08:00 committed by GitHub
parent 4203540cb0
commit 2625b389b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 90 additions and 76 deletions

View File

@ -58,12 +58,12 @@ namespace FunctionalTests
{
OnMessageReceived = context =>
{
var signalRTokenHeader = context.Request.Query["signalRTokenHeader"];
var signalRTokenHeader = context.Request.Query["access_token"];
if (!string.IsNullOrEmpty(signalRTokenHeader) &&
(context.HttpContext.WebSockets.IsWebSocketRequest || context.Request.Headers["Accept"] == "text/event-stream"))
{
context.Token = context.Request.Query["signalRTokenHeader"];
context.Token = context.Request.Query["access_token"];
}
return Task.CompletedTask;
}

View File

@ -476,7 +476,7 @@ describe('hubConnection', function () {
hubConnection = new HubConnection('/authorizedhub', {
transport: transportType,
logger: LogLevel.Trace,
accessToken: () => jwtToken
accessTokenFactory: () => jwtToken
});
hubConnection.onclose(function (error) {
expect(error).toBe(undefined);

View File

@ -9,25 +9,29 @@ import { ITransport, TransportType, TransferMode } from "../src/Transports"
import { eachTransport, eachEndpointUrl } from "./Common";
import { HttpResponse } from "../src/index";
const commonOptions: IHttpConnectionOptions = {
logger: null
};
describe("HttpConnection", () => {
it("cannot be created with relative url if document object is not present", () => {
expect(() => new HttpConnection("/test"))
expect(() => new HttpConnection("/test", commonOptions))
.toThrow(new Error("Cannot resolve '/test'."));
});
it("cannot be created with relative url if window object is not present", () => {
(<any>global).window = {};
expect(() => new HttpConnection("/test"))
expect(() => new HttpConnection("/test", commonOptions))
.toThrow(new Error("Cannot resolve '/test'."));
delete (<any>global).window;
});
it("starting connection fails if getting id fails", async (done) => {
let options: IHttpConnectionOptions = {
...commonOptions,
httpClient: new TestHttpClient()
.on("POST", r => Promise.reject("error"))
.on("GET", r => ""),
logger: null
} as IHttpConnectionOptions;
let connection = new HttpConnection("http://tempuri.org", options);
@ -45,6 +49,7 @@ describe("HttpConnection", () => {
it("cannot start a running connection", async (done) => {
let options: IHttpConnectionOptions = {
...commonOptions,
httpClient: new TestHttpClient()
.on("POST", r => {
connection.start()
@ -58,7 +63,6 @@ describe("HttpConnection", () => {
});
return Promise.reject("error");
}),
logger: null
} as IHttpConnectionOptions;
let connection = new HttpConnection("http://tempuri.org", options);
@ -75,13 +79,13 @@ describe("HttpConnection", () => {
it("can start a stopped connection", async (done) => {
let negotiateCalls = 0;
let options: IHttpConnectionOptions = {
...commonOptions,
httpClient: new TestHttpClient()
.on("POST", r => {
negotiateCalls += 1;
return Promise.reject("reached negotiate");
})
.on("GET", r => ""),
logger: null
} as IHttpConnectionOptions;
let connection = new HttpConnection("http://tempuri.org", options);
@ -103,6 +107,7 @@ describe("HttpConnection", () => {
it("can stop a starting connection", async (done) => {
let options: IHttpConnectionOptions = {
...commonOptions,
httpClient: new TestHttpClient()
.on("POST", r => {
connection.stop();
@ -112,7 +117,6 @@ describe("HttpConnection", () => {
connection.stop();
return "";
}),
logger: null
} as IHttpConnectionOptions;
let connection = new HttpConnection("http://tempuri.org", options);
@ -128,7 +132,7 @@ describe("HttpConnection", () => {
});
it("can stop a non-started connection", async (done) => {
let connection = new HttpConnection("http://tempuri.org");
let connection = new HttpConnection("http://tempuri.org", commonOptions);
await connection.stop();
done();
});
@ -151,11 +155,11 @@ describe("HttpConnection", () => {
}
let options: IHttpConnectionOptions = {
...commonOptions,
httpClient: new TestHttpClient()
.on("POST", r => "{ \"connectionId\": \"42\" }")
.on("GET", r => ""),
transport: fakeTransport,
logger: null,
} as IHttpConnectionOptions;
@ -178,6 +182,7 @@ describe("HttpConnection", () => {
let negotiateUrl: string;
let connection: HttpConnection;
let options: IHttpConnectionOptions = {
...commonOptions,
httpClient: new TestHttpClient()
.on("POST", r => {
negotiateUrl = r.url;
@ -188,7 +193,6 @@ describe("HttpConnection", () => {
connection.stop();
return "";
}),
logger: null
} as IHttpConnectionOptions;
connection = new HttpConnection(givenUrl, options);
@ -212,11 +216,11 @@ describe("HttpConnection", () => {
}
it(`cannot be started if requested ${TransportType[requestedTransport]} transport not available on server`, async done => {
let options: IHttpConnectionOptions = {
...commonOptions,
httpClient: new TestHttpClient()
.on("POST", r => "{ \"connectionId\": \"42\", \"availableTransports\": [] }")
.on("GET", r => ""),
transport: requestedTransport,
logger: null
} as IHttpConnectionOptions;
let connection = new HttpConnection("http://tempuri.org", options);
@ -234,10 +238,10 @@ describe("HttpConnection", () => {
it("cannot be started if no transport available on server and no transport requested", async done => {
let options: IHttpConnectionOptions = {
...commonOptions,
httpClient: new TestHttpClient()
.on("POST", r => "{ \"connectionId\": \"42\", \"availableTransports\": [] }")
.on("GET", r => ""),
logger: null
} as IHttpConnectionOptions;
let connection = new HttpConnection("http://tempuri.org", options);
@ -254,9 +258,9 @@ describe("HttpConnection", () => {
it('does not send negotiate request if WebSockets transport requested explicitly', async done => {
let options: IHttpConnectionOptions = {
...commonOptions,
httpClient: new TestHttpClient(),
transport: TransportType.WebSockets,
logger: null
} as IHttpConnectionOptions;
let connection = new HttpConnection("http://tempuri.org", options);
@ -291,11 +295,11 @@ describe("HttpConnection", () => {
} as ITransport;
let options: IHttpConnectionOptions = {
...commonOptions,
httpClient: new TestHttpClient()
.on("POST", r => "{ \"connectionId\": \"42\", \"availableTransports\": [] }")
.on("GET", r => ""),
transport: fakeTransport,
logger: null
} as IHttpConnectionOptions;
let connection = new HttpConnection("https://tempuri.org", options);

View File

@ -13,12 +13,16 @@ import { MessageType } from "../src/IHubProtocol"
import { asyncit as it, captureException, delay, PromiseSource } from './Utils';
import { IHubConnectionOptions } from "../src/HubConnection";
const commonOptions: IHubConnectionOptions = {
logger: null,
}
describe("HubConnection", () => {
describe("start", () => {
it("sends negotiation message", async () => {
let connection = new TestConnection();
let hubConnection = new HubConnection(connection, { logger: null });
let hubConnection = new HubConnection(connection, commonOptions);
await hubConnection.start();
expect(connection.sentData.length).toBe(1)
expect(JSON.parse(connection.sentData[0])).toEqual({
@ -32,7 +36,7 @@ describe("HubConnection", () => {
it("sends a non blocking invocation", async () => {
let connection = new TestConnection();
let hubConnection = new HubConnection(connection, { logger: null });
let hubConnection = new HubConnection(connection, commonOptions);
let invokePromise = hubConnection.send("testMethod", "arg", 42)
.catch((_) => { }); // Suppress exception and unhandled promise rejection warning.
@ -56,7 +60,7 @@ describe("HubConnection", () => {
it("sends an invocation", async () => {
let connection = new TestConnection();
let hubConnection = new HubConnection(connection, { logger: null });
let hubConnection = new HubConnection(connection, commonOptions);
let invokePromise = hubConnection.invoke("testMethod", "arg", 42)
.catch((_) => { }); // Suppress exception and unhandled promise rejection warning.
@ -79,7 +83,7 @@ describe("HubConnection", () => {
it("rejects the promise when an error is received", async () => {
let connection = new TestConnection();
let hubConnection = new HubConnection(connection, { logger: null });
let hubConnection = new HubConnection(connection, commonOptions);
let invokePromise = hubConnection.invoke("testMethod", "arg", 42);
connection.receive({ type: MessageType.Completion, invocationId: connection.lastInvocationId, error: "foo" });
@ -91,7 +95,7 @@ describe("HubConnection", () => {
it("resolves the promise when a result is received", async () => {
let connection = new TestConnection();
let hubConnection = new HubConnection(connection, { logger: null });
let hubConnection = new HubConnection(connection, commonOptions);
let invokePromise = hubConnection.invoke("testMethod", "arg", 42);
connection.receive({ type: MessageType.Completion, invocationId: connection.lastInvocationId, result: "foo" });
@ -102,7 +106,7 @@ describe("HubConnection", () => {
it("completes pending invocations when stopped", async () => {
let connection = new TestConnection();
let hubConnection = new HubConnection(connection, { logger: null });
let hubConnection = new HubConnection(connection, commonOptions);
let invokePromise = hubConnection.invoke("testMethod");
hubConnection.stop();
@ -113,7 +117,7 @@ describe("HubConnection", () => {
it("completes pending invocations when connection is lost", async () => {
let connection = new TestConnection();
let hubConnection = new HubConnection(connection, { logger: null });
let hubConnection = new HubConnection(connection, commonOptions);
let invokePromise = hubConnection.invoke("testMethod");
// Typically this would be called by the transport
connection.onclose(new Error("Connection lost"));
@ -149,7 +153,7 @@ describe("HubConnection", () => {
it("callback invoked when servers invokes a method on the client", async () => {
let connection = new TestConnection();
let hubConnection = new HubConnection(connection, { logger: null });
let hubConnection = new HubConnection(connection, commonOptions);
let value = "";
hubConnection.on("message", v => value = v);
@ -166,7 +170,7 @@ describe("HubConnection", () => {
it("can have multiple callbacks", async () => {
let connection = new TestConnection();
let hubConnection = new HubConnection(connection, { logger: null });
let hubConnection = new HubConnection(connection, commonOptions);
let numInvocations1 = 0;
let numInvocations2 = 0;
hubConnection.on("message", () => numInvocations1++);
@ -186,7 +190,7 @@ describe("HubConnection", () => {
it("can unsubscribe from on", async () => {
let connection = new TestConnection();
let hubConnection = new HubConnection(connection, { logger: null });
let hubConnection = new HubConnection(connection, commonOptions);
var numInvocations = 0;
var callback = () => numInvocations++;
@ -215,7 +219,7 @@ describe("HubConnection", () => {
it("unsubscribing from non-existing callbacks no-ops", async () => {
let connection = new TestConnection();
let hubConnection = new HubConnection(connection, { logger: null });
let hubConnection = new HubConnection(connection, commonOptions);
hubConnection.off("_", () => { });
hubConnection.on("message", t => { });
@ -267,7 +271,7 @@ describe("HubConnection", () => {
it("sends an invocation", async () => {
let connection = new TestConnection();
let hubConnection = new HubConnection(connection, { logger: null });
let hubConnection = new HubConnection(connection, commonOptions);
let invokePromise = hubConnection.stream("testStream", "arg", 42);
// Verify the message is sent
@ -289,7 +293,7 @@ describe("HubConnection", () => {
it("completes with an error when an error is yielded", async () => {
let connection = new TestConnection();
let hubConnection = new HubConnection(connection, { logger: null });
let hubConnection = new HubConnection(connection, commonOptions);
let observer = new TestObserver();
hubConnection.stream<any>("testMethod", "arg", 42)
.subscribe(observer);
@ -303,7 +307,7 @@ describe("HubConnection", () => {
it("completes the observer when a completion is received", async () => {
let connection = new TestConnection();
let hubConnection = new HubConnection(connection, { logger: null });
let hubConnection = new HubConnection(connection, commonOptions);
let observer = new TestObserver();
hubConnection.stream<any>("testMethod", "arg", 42)
.subscribe(observer);
@ -316,7 +320,7 @@ describe("HubConnection", () => {
it("completes pending streams when stopped", async () => {
let connection = new TestConnection();
let hubConnection = new HubConnection(connection, { logger: null });
let hubConnection = new HubConnection(connection, commonOptions);
let observer = new TestObserver();
hubConnection.stream<any>("testMethod")
.subscribe(observer);
@ -329,7 +333,7 @@ describe("HubConnection", () => {
it("completes pending streams when connection is lost", async () => {
let connection = new TestConnection();
let hubConnection = new HubConnection(connection, { logger: null });
let hubConnection = new HubConnection(connection, commonOptions);
let observer = new TestObserver();
hubConnection.stream<any>("testMethod")
.subscribe(observer);
@ -344,7 +348,7 @@ describe("HubConnection", () => {
it("yields items as they arrive", async () => {
let connection = new TestConnection();
let hubConnection = new HubConnection(connection, { logger: null });
let hubConnection = new HubConnection(connection, commonOptions);
let observer = new TestObserver();
hubConnection.stream<any>("testMethod")
.subscribe(observer);
@ -365,7 +369,7 @@ describe("HubConnection", () => {
it("does not require error function registered", async () => {
let connection = new TestConnection();
let hubConnection = new HubConnection(connection, { logger: null });
let hubConnection = new HubConnection(connection, commonOptions);
let observer = hubConnection.stream("testMethod").subscribe({
next: val => { }
});
@ -378,7 +382,7 @@ describe("HubConnection", () => {
it("does not require complete function registered", async () => {
let connection = new TestConnection();
let hubConnection = new HubConnection(connection, { logger: null });
let hubConnection = new HubConnection(connection, commonOptions);
let observer = hubConnection.stream("testMethod").subscribe({
next: val => { }
});
@ -391,7 +395,7 @@ describe("HubConnection", () => {
it("can be canceled", () => {
let connection = new TestConnection();
let hubConnection = new HubConnection(connection, { logger: null });
let hubConnection = new HubConnection(connection, commonOptions);
let observer = new TestObserver();
let subscription = hubConnection.stream("testMethod")
.subscribe(observer);
@ -417,7 +421,7 @@ describe("HubConnection", () => {
describe("onClose", () => {
it("can have multiple callbacks", async () => {
let connection = new TestConnection();
let hubConnection = new HubConnection(connection, { logger: null });
let hubConnection = new HubConnection(connection, commonOptions);
let invocations = 0;
hubConnection.onclose(e => invocations++);
hubConnection.onclose(e => invocations++);
@ -428,7 +432,7 @@ describe("HubConnection", () => {
it("callbacks receive error", async () => {
let connection = new TestConnection();
let hubConnection = new HubConnection(connection, { logger: null });
let hubConnection = new HubConnection(connection, commonOptions);
let error: Error;
hubConnection.onclose(e => error = e);
@ -439,7 +443,7 @@ describe("HubConnection", () => {
it("ignores null callbacks", async () => {
let connection = new TestConnection();
let hubConnection = new HubConnection(connection, { logger: null });
let hubConnection = new HubConnection(connection, commonOptions);
hubConnection.onclose(null);
hubConnection.onclose(undefined);
// Typically this would be called by the transport
@ -453,7 +457,7 @@ describe("HubConnection", () => {
// Receive the ping mid-invocation so we can see that the rest of the flow works fine
let connection = new TestConnection();
let hubConnection = new HubConnection(connection, { logger: null });
let hubConnection = new HubConnection(connection, commonOptions);
let invokePromise = hubConnection.invoke("testMethod", "arg", 42);
connection.receive({ type: MessageType.Ping });
@ -464,7 +468,7 @@ describe("HubConnection", () => {
it("does not terminate if messages are received", async () => {
let connection = new TestConnection();
let hubConnection = new HubConnection(connection, { timeoutInMilliseconds: 100, logger: null });
let hubConnection = new HubConnection(connection, { ...commonOptions, timeoutInMilliseconds: 100 });
let p = new PromiseSource<Error>();
hubConnection.onclose(error => p.resolve(error));
@ -489,7 +493,7 @@ describe("HubConnection", () => {
it("terminates if no messages received within timeout interval", async () => {
let connection = new TestConnection();
let hubConnection = new HubConnection(connection, { timeoutInMilliseconds: 100, logger: null });
let hubConnection = new HubConnection(connection, { ...commonOptions, timeoutInMilliseconds: 100 });
let p = new PromiseSource<Error>();
hubConnection.onclose(error => p.resolve(error));

View File

@ -12,7 +12,7 @@ export interface IHttpConnectionOptions {
httpClient?: HttpClient;
transport?: TransportType | ITransport;
logger?: ILogger | LogLevel;
accessToken?: () => string;
accessTokenFactory?: () => string;
}
const enum ConnectionState {
@ -42,7 +42,10 @@ export class HttpConnection implements IConnection {
constructor(url: string, options: IHttpConnectionOptions = {}) {
this.logger = LoggerFactory.createLogger(options.logger);
this.baseUrl = this.resolveUrl(url);
options = options || {};
options.accessTokenFactory = options.accessTokenFactory || (() => null);
this.httpClient = options.httpClient || new DefaultHttpClient();
this.connectionState = ConnectionState.Disconnected;
this.options = options;
@ -68,9 +71,10 @@ export class HttpConnection implements IConnection {
}
else {
let headers;
if (this.options.accessToken) {
let token = this.options.accessTokenFactory();
if (token) {
headers = new Map<string, string>();
headers.set("Authorization", `Bearer ${this.options.accessToken()}`);
headers.set("Authorization", `Bearer ${token}`);
}
let negotiatePayload = await this.httpClient.post(this.resolveNegotiateUrl(this.baseUrl), {
@ -119,13 +123,13 @@ export class HttpConnection implements IConnection {
transport = TransportType[availableTransports[0]];
}
if (transport === TransportType.WebSockets && availableTransports.indexOf(TransportType[transport]) >= 0) {
return new WebSocketTransport(this.options.accessToken, this.logger);
return new WebSocketTransport(this.options.accessTokenFactory, this.logger);
}
if (transport === TransportType.ServerSentEvents && availableTransports.indexOf(TransportType[transport]) >= 0) {
return new ServerSentEventsTransport(this.httpClient, this.options.accessToken, this.logger);
return new ServerSentEventsTransport(this.httpClient, this.options.accessTokenFactory, this.logger);
}
if (transport === TransportType.LongPolling && availableTransports.indexOf(TransportType[transport]) >= 0) {
return new LongPollingTransport(this.httpClient, this.options.accessToken, this.logger);
return new LongPollingTransport(this.httpClient, this.options.accessTokenFactory, this.logger);
}
if (this.isITransport(transport)) {

View File

@ -29,21 +29,21 @@ export interface ITransport {
export class WebSocketTransport implements ITransport {
private readonly logger: ILogger;
private readonly accessToken: () => string;
private readonly accessTokenFactory: () => string;
private webSocket: WebSocket;
constructor(accessToken: () => string, logger: ILogger) {
constructor(accessTokenFactory: () => string, logger: ILogger) {
this.logger = logger;
this.accessToken = accessToken;
this.accessTokenFactory = accessTokenFactory || (() => null);
}
connect(url: string, requestedTransferMode: TransferMode, connection: IConnection): Promise<TransferMode> {
return new Promise<TransferMode>((resolve, reject) => {
url = url.replace(/^http/, "ws");
if (this.accessToken) {
let token = this.accessToken();
url += (url.indexOf("?") < 0 ? "?" : "&") + `signalRTokenHeader=${token}`;
let token = this.accessTokenFactory();
if (token) {
url += (url.indexOf("?") < 0 ? "?" : "&") + `access_token=${encodeURIComponent(token)}`;
}
let webSocket = new WebSocket(url);
@ -105,14 +105,14 @@ export class WebSocketTransport implements ITransport {
export class ServerSentEventsTransport implements ITransport {
private readonly httpClient: HttpClient;
private readonly accessToken: () => string;
private readonly accessTokenFactory: () => string;
private readonly logger: ILogger;
private eventSource: EventSource;
private url: string;
constructor(httpClient: HttpClient, accessToken: () => string, logger: ILogger) {
constructor(httpClient: HttpClient, accessTokenFactory: () => string, logger: ILogger) {
this.httpClient = httpClient;
this.accessToken = accessToken;
this.accessTokenFactory = accessTokenFactory || (() => null);
this.logger = logger;
}
@ -123,9 +123,9 @@ export class ServerSentEventsTransport implements ITransport {
this.url = url;
return new Promise<TransferMode>((resolve, reject) => {
if (this.accessToken) {
let token = this.accessToken();
url += (url.indexOf("?") < 0 ? "?" : "&") + `signalRTokenHeader=${token}`;
let token = this.accessTokenFactory();
if (token) {
url += (url.indexOf("?") < 0 ? "?" : "&") + `access_token=${encodeURIComponent(token)}`;
}
let eventSource = new EventSource(url);
@ -145,7 +145,7 @@ export class ServerSentEventsTransport implements ITransport {
}
};
eventSource.onerror = (e: ErrorEvent) => {
eventSource.onerror = (e: any) => {
reject();
// don't report an error if the transport did not start successfully
@ -168,7 +168,7 @@ export class ServerSentEventsTransport implements ITransport {
}
async send(data: any): Promise<void> {
return send(this.httpClient, this.url, this.accessToken, data);
return send(this.httpClient, this.url, this.accessTokenFactory, data);
}
stop(): Promise<void> {
@ -185,16 +185,16 @@ export class ServerSentEventsTransport implements ITransport {
export class LongPollingTransport implements ITransport {
private readonly httpClient: HttpClient;
private readonly accessToken: () => string;
private readonly accessTokenFactory: () => string;
private readonly logger: ILogger;
private url: string;
private pollXhr: XMLHttpRequest;
private pollAbort: AbortController;
constructor(httpClient: HttpClient, accessToken: () => string, logger: ILogger) {
constructor(httpClient: HttpClient, accessTokenFactory: () => string, logger: ILogger) {
this.httpClient = httpClient;
this.accessToken = accessToken;
this.accessTokenFactory = accessTokenFactory || (() => null);
this.logger = logger;
this.pollAbort = new AbortController();
}
@ -225,8 +225,9 @@ export class LongPollingTransport implements ITransport {
pollOptions.responseType = "arraybuffer";
}
if (this.accessToken) {
pollOptions.headers.set("Authorization", `Bearer ${this.accessToken()}`);
let token = this.accessTokenFactory();
if (token) {
pollOptions.headers.set("Authorization", `Bearer ${token}`);
}
while (!this.pollAbort.signal.aborted) {
@ -281,7 +282,7 @@ export class LongPollingTransport implements ITransport {
}
async send(data: any): Promise<void> {
return send(this.httpClient, this.url, this.accessToken, data);
return send(this.httpClient, this.url, this.accessTokenFactory, data);
}
stop(): Promise<void> {
@ -293,11 +294,12 @@ export class LongPollingTransport implements ITransport {
onclose: TransportClosed;
}
async function send(httpClient: HttpClient, url: string, accessToken: () => string, content: string | ArrayBuffer): Promise<void> {
async function send(httpClient: HttpClient, url: string, accessTokenFactory: () => string, content: string | ArrayBuffer): Promise<void> {
let headers;
if (accessToken) {
let token = accessTokenFactory();
if (token) {
headers = new Map<string, string>();
headers.set("Authorization", `Bearer ${accessToken()}`)
headers.set("Authorization", `Bearer ${accessTokenFactory()}`)
}
await httpClient.post(url, {

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
@ -50,12 +50,12 @@ namespace JwtSample
{
OnMessageReceived = context =>
{
var signalRTokenHeader = context.Request.Query["signalRTokenHeader"];
var accessToken = context.Request.Query["access_token"];
if (!string.IsNullOrEmpty(signalRTokenHeader) &&
if (!string.IsNullOrEmpty(accessToken) &&
(context.HttpContext.WebSockets.IsWebSocketRequest || context.Request.Headers["Accept"] == "text/event-stream"))
{
context.Token = context.Request.Query["signalRTokenHeader"];
context.Token = context.Request.Query["access_token"];
}
return Task.CompletedTask;
}

View File

@ -68,7 +68,7 @@
.then(function () {
var options = {
transport: transportType,
accessToken: function () { return tokens[clientId]; }
accessTokenFactory: function () { return tokens[clientId]; }
};
connection = new signalR.HubConnection('/broadcast', options);