mirror of
https://github.com/wkulhanek/bonob.git
synced 2025-12-21 17:33:29 +01:00
Fix bug where authorisation token being truncated by sonos (#86)
This commit is contained in:
@@ -39,7 +39,6 @@ import { axiosImageFetcher, ImageFetcher } from "./subsonic";
|
|||||||
import {
|
import {
|
||||||
JWTSmapiLoginTokens,
|
JWTSmapiLoginTokens,
|
||||||
SmapiAuthTokens,
|
SmapiAuthTokens,
|
||||||
smapiTokenFromString,
|
|
||||||
} from "./smapi_auth";
|
} from "./smapi_auth";
|
||||||
|
|
||||||
export const BONOB_ACCESS_TOKEN_HEADER = "bat";
|
export const BONOB_ACCESS_TOKEN_HEADER = "bat";
|
||||||
@@ -378,28 +377,23 @@ function server(
|
|||||||
logger.info(
|
logger.info(
|
||||||
`${trace} bnb<- ${req.method} ${req.path}?${JSON.stringify(
|
`${trace} bnb<- ${req.method} ${req.path}?${JSON.stringify(
|
||||||
req.query
|
req.query
|
||||||
)}, headers=${JSON.stringify({ ...req.headers, authorization: "*****" })}`
|
)}, headers=${JSON.stringify({ ...req.headers, "bnbt": "*****", "bnbk": "*****" })}`
|
||||||
);
|
);
|
||||||
|
|
||||||
const authHeader = E.fromNullable("Missing header");
|
|
||||||
const bearerToken = E.fromNullable("No Bearer token");
|
|
||||||
const serviceToken = pipe(
|
const serviceToken = pipe(
|
||||||
authHeader(req.headers["authorization"] as string),
|
E.fromNullable("Missing bnbt header")(req.headers["bnbt"] as string),
|
||||||
E.chain((authorization) =>
|
E.chain(token => pipe(
|
||||||
|
E.fromNullable("Missing bnbk header")(req.headers["bnbk"] as string),
|
||||||
|
E.map(key => ({ token, key }))
|
||||||
|
)),
|
||||||
|
E.chain((auth) =>
|
||||||
pipe(
|
pipe(
|
||||||
authorization.match(/Bearer (?<token>.*)/),
|
smapiAuthTokens.verify(auth),
|
||||||
bearerToken,
|
E.mapLeft((_) => "Auth token failed to verify")
|
||||||
E.map((match) => match[1]!)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
E.chain((bearerToken) =>
|
|
||||||
pipe(
|
|
||||||
smapiAuthTokens.verify(smapiTokenFromString(bearerToken)),
|
|
||||||
E.mapLeft((_) => "Bearer token failed to verify")
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
E.getOrElseW(() => undefined)
|
E.getOrElseW(() => undefined)
|
||||||
);
|
)
|
||||||
|
|
||||||
if (!serviceToken) {
|
if (!serviceToken) {
|
||||||
return res.status(401).send();
|
return res.status(401).send();
|
||||||
|
|||||||
13
src/smapi.ts
13
src/smapi.ts
@@ -32,7 +32,6 @@ import {
|
|||||||
isExpiredTokenError,
|
isExpiredTokenError,
|
||||||
MissingLoginTokenError,
|
MissingLoginTokenError,
|
||||||
SmapiAuthTokens,
|
SmapiAuthTokens,
|
||||||
smapiTokenAsString,
|
|
||||||
SMAPI_FAULT_LOGIN_UNAUTHORIZED,
|
SMAPI_FAULT_LOGIN_UNAUTHORIZED,
|
||||||
ToSmapiFault,
|
ToSmapiFault,
|
||||||
} from "./smapi_auth";
|
} from "./smapi_auth";
|
||||||
@@ -532,10 +531,14 @@ function bindSmapiSoapServiceToExpress(
|
|||||||
httpHeaders: [
|
httpHeaders: [
|
||||||
{
|
{
|
||||||
httpHeader: {
|
httpHeader: {
|
||||||
header: "Authorization",
|
header: "bnbt",
|
||||||
value: `Bearer ${smapiTokenAsString(
|
value: credentials.loginToken.token,
|
||||||
credentials.loginToken
|
},
|
||||||
)}`,
|
},
|
||||||
|
{
|
||||||
|
httpHeader: {
|
||||||
|
header: "bnbk",
|
||||||
|
value: credentials.loginToken.key,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import i8n, { randomLang } from "../src/i8n";
|
|||||||
import { SONOS_RECOMMENDED_IMAGE_SIZES } from "../src/smapi";
|
import { SONOS_RECOMMENDED_IMAGE_SIZES } from "../src/smapi";
|
||||||
import { Clock, SystemClock } from "../src/clock";
|
import { Clock, SystemClock } from "../src/clock";
|
||||||
import { formatForURL } from "../src/burn";
|
import { formatForURL } from "../src/burn";
|
||||||
import { ExpiredTokenError, SmapiAuthTokens, SmapiToken, smapiTokenAsString } from "../src/smapi_auth";
|
import { ExpiredTokenError, SmapiAuthTokens, SmapiToken } from "../src/smapi_auth";
|
||||||
|
|
||||||
describe("rangeFilterFor", () => {
|
describe("rangeFilterFor", () => {
|
||||||
describe("invalid range header string", () => {
|
describe("invalid range header string", () => {
|
||||||
@@ -754,7 +754,7 @@ describe("server", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when the Bearer token has expired", () => {
|
describe("when the authorization has expired", () => {
|
||||||
it("should return a 401", async () => {
|
it("should return a 401", async () => {
|
||||||
smapiAuthTokens.verify.mockReturnValue(E.left(new ExpiredTokenError(serviceToken)))
|
smapiAuthTokens.verify.mockReturnValue(E.left(new ExpiredTokenError(serviceToken)))
|
||||||
|
|
||||||
@@ -764,13 +764,15 @@ describe("server", () => {
|
|||||||
pathname: `/stream/track/${trackId}`
|
pathname: `/stream/track/${trackId}`
|
||||||
})
|
})
|
||||||
.path(),
|
.path(),
|
||||||
).set('Authorization', `Bearer ${smapiTokenAsString(smapiAuthToken)}`);
|
)
|
||||||
|
.set('bnbt', smapiAuthToken.token)
|
||||||
|
.set('bnbk', smapiAuthToken.key);
|
||||||
|
|
||||||
expect(res.status).toEqual(401);
|
expect(res.status).toEqual(401);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when the Bearer token is valid", () => {
|
describe("when the authorization token & key are valid", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
smapiAuthTokens.verify.mockReturnValue(E.right(serviceToken));
|
smapiAuthTokens.verify.mockReturnValue(E.right(serviceToken));
|
||||||
});
|
});
|
||||||
@@ -795,7 +797,9 @@ describe("server", () => {
|
|||||||
bonobUrl
|
bonobUrl
|
||||||
.append({ pathname: `/stream/track/${trackId}`})
|
.append({ pathname: `/stream/track/${trackId}`})
|
||||||
.path()
|
.path()
|
||||||
).set('Authorization', `Bearer ${smapiTokenAsString(smapiAuthToken)}`);
|
)
|
||||||
|
.set('bnbt', smapiAuthToken.token)
|
||||||
|
.set('bnbk', smapiAuthToken.key);
|
||||||
|
|
||||||
expect(res.status).toEqual(trackStream.status);
|
expect(res.status).toEqual(trackStream.status);
|
||||||
expect(res.headers["content-type"]).toEqual(
|
expect(res.headers["content-type"]).toEqual(
|
||||||
@@ -821,8 +825,10 @@ describe("server", () => {
|
|||||||
.head(bonobUrl
|
.head(bonobUrl
|
||||||
.append({ pathname: `/stream/track/${trackId}` })
|
.append({ pathname: `/stream/track/${trackId}` })
|
||||||
.path()
|
.path()
|
||||||
)
|
)
|
||||||
.set('Authorization', `Bearer ${smapiTokenAsString(smapiAuthToken)}`);
|
.set('bnbt', smapiAuthToken.token)
|
||||||
|
.set('bnbk', smapiAuthToken.key);
|
||||||
|
|
||||||
|
|
||||||
expect(res.status).toEqual(404);
|
expect(res.status).toEqual(404);
|
||||||
expect(res.body).toEqual({});
|
expect(res.body).toEqual({});
|
||||||
@@ -851,13 +857,15 @@ describe("server", () => {
|
|||||||
bonobUrl
|
bonobUrl
|
||||||
.append({ pathname: `/stream/track/${trackId}` })
|
.append({ pathname: `/stream/track/${trackId}` })
|
||||||
.path()
|
.path()
|
||||||
).set('Authorization', `Bearer ${smapiTokenAsString(smapiAuthToken)}`);
|
)
|
||||||
|
.set('bnbt', smapiAuthToken.token)
|
||||||
|
.set('bnbk', smapiAuthToken.key);
|
||||||
|
|
||||||
expect(res.status).toEqual(401);
|
expect(res.status).toEqual(401);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when the Bearer token is valid", () => {
|
describe("when the authorization token & key is valid", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
smapiAuthTokens.verify.mockReturnValue(E.right(serviceToken));
|
smapiAuthTokens.verify.mockReturnValue(E.right(serviceToken));
|
||||||
});
|
});
|
||||||
@@ -878,7 +886,9 @@ describe("server", () => {
|
|||||||
bonobUrl
|
bonobUrl
|
||||||
.append({ pathname: `/stream/track/${trackId}` })
|
.append({ pathname: `/stream/track/${trackId}` })
|
||||||
.path()
|
.path()
|
||||||
).set('Authorization', `Bearer ${smapiTokenAsString(smapiAuthToken)}`);
|
)
|
||||||
|
.set('bnbt', smapiAuthToken.token)
|
||||||
|
.set('bnbk', smapiAuthToken.key);
|
||||||
|
|
||||||
expect(res.status).toEqual(404);
|
expect(res.status).toEqual(404);
|
||||||
|
|
||||||
@@ -910,7 +920,9 @@ describe("server", () => {
|
|||||||
bonobUrl
|
bonobUrl
|
||||||
.append({ pathname: `/stream/track/${trackId}` })
|
.append({ pathname: `/stream/track/${trackId}` })
|
||||||
.path()
|
.path()
|
||||||
).set('Authorization', `Bearer ${smapiTokenAsString(smapiAuthToken)}`);
|
)
|
||||||
|
.set('bnbt', smapiAuthToken.token)
|
||||||
|
.set('bnbk', smapiAuthToken.key);
|
||||||
|
|
||||||
expect(res.status).toEqual(stream.status);
|
expect(res.status).toEqual(stream.status);
|
||||||
expect(res.headers["content-type"]).toEqual(
|
expect(res.headers["content-type"]).toEqual(
|
||||||
@@ -950,7 +962,9 @@ describe("server", () => {
|
|||||||
bonobUrl
|
bonobUrl
|
||||||
.append({ pathname: `/stream/track/${trackId}` })
|
.append({ pathname: `/stream/track/${trackId}` })
|
||||||
.path()
|
.path()
|
||||||
).set('Authorization', `Bearer ${smapiTokenAsString(smapiAuthToken)}`);
|
)
|
||||||
|
.set('bnbt', smapiAuthToken.token)
|
||||||
|
.set('bnbk', smapiAuthToken.key);
|
||||||
|
|
||||||
expect(res.status).toEqual(stream.status);
|
expect(res.status).toEqual(stream.status);
|
||||||
expect(res.headers["content-type"]).toEqual(
|
expect(res.headers["content-type"]).toEqual(
|
||||||
@@ -988,7 +1002,9 @@ describe("server", () => {
|
|||||||
bonobUrl
|
bonobUrl
|
||||||
.append({ pathname: `/stream/track/${trackId}` })
|
.append({ pathname: `/stream/track/${trackId}` })
|
||||||
.path()
|
.path()
|
||||||
).set('Authorization', `Bearer ${smapiTokenAsString(smapiAuthToken)}`);
|
)
|
||||||
|
.set('bnbt', smapiAuthToken.token)
|
||||||
|
.set('bnbk', smapiAuthToken.key);
|
||||||
|
|
||||||
expect(res.status).toEqual(stream.status);
|
expect(res.status).toEqual(stream.status);
|
||||||
expect(res.header["content-type"]).toEqual(
|
expect(res.header["content-type"]).toEqual(
|
||||||
@@ -1027,7 +1043,9 @@ describe("server", () => {
|
|||||||
bonobUrl
|
bonobUrl
|
||||||
.append({ pathname: `/stream/track/${trackId}` })
|
.append({ pathname: `/stream/track/${trackId}` })
|
||||||
.path()
|
.path()
|
||||||
).set('Authorization', `Bearer ${smapiTokenAsString(smapiAuthToken)}`);
|
)
|
||||||
|
.set('bnbt', smapiAuthToken.token)
|
||||||
|
.set('bnbk', smapiAuthToken.key);
|
||||||
|
|
||||||
expect(res.status).toEqual(stream.status);
|
expect(res.status).toEqual(stream.status);
|
||||||
expect(res.header["content-type"]).toEqual(
|
expect(res.header["content-type"]).toEqual(
|
||||||
@@ -1072,7 +1090,8 @@ describe("server", () => {
|
|||||||
.append({ pathname: `/stream/track/${trackId}` })
|
.append({ pathname: `/stream/track/${trackId}` })
|
||||||
.path()
|
.path()
|
||||||
)
|
)
|
||||||
.set('Authorization', `Bearer ${smapiTokenAsString(smapiAuthToken)}`)
|
.set('bnbt', smapiAuthToken.token)
|
||||||
|
.set('bnbk', smapiAuthToken.key)
|
||||||
.set("Range", requestedRange);
|
.set("Range", requestedRange);
|
||||||
|
|
||||||
expect(res.status).toEqual(stream.status);
|
expect(res.status).toEqual(stream.status);
|
||||||
@@ -1116,7 +1135,8 @@ describe("server", () => {
|
|||||||
.append({ pathname: `/stream/track/${trackId}` })
|
.append({ pathname: `/stream/track/${trackId}` })
|
||||||
.path()
|
.path()
|
||||||
)
|
)
|
||||||
.set('Authorization', `Bearer ${smapiTokenAsString(smapiAuthToken)}`)
|
.set('bnbt', smapiAuthToken.token)
|
||||||
|
.set('bnbk', smapiAuthToken.key)
|
||||||
.set("Range", "4000-5000");
|
.set("Range", "4000-5000");
|
||||||
|
|
||||||
expect(res.status).toEqual(stream.status);
|
expect(res.status).toEqual(stream.status);
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ import { iconForGenre } from "../src/icon";
|
|||||||
import { formatForURL } from "../src/burn";
|
import { formatForURL } from "../src/burn";
|
||||||
import { range } from "underscore";
|
import { range } from "underscore";
|
||||||
import { FixedClock } from "../src/clock";
|
import { FixedClock } from "../src/clock";
|
||||||
import { ExpiredTokenError, InvalidTokenError, SmapiAuthTokens, SmapiToken, smapiTokenAsString, ToSmapiFault } from "../src/smapi_auth";
|
import { ExpiredTokenError, InvalidTokenError, SmapiAuthTokens, SmapiToken, ToSmapiFault } from "../src/smapi_auth";
|
||||||
|
|
||||||
const parseXML = (value: string) => new DOMParserImpl().parseFromString(value);
|
const parseXML = (value: string) => new DOMParserImpl().parseFromString(value);
|
||||||
|
|
||||||
@@ -3046,14 +3046,20 @@ describe("wsdl api", () => {
|
|||||||
pathname: `/stream/track/${trackId}`,
|
pathname: `/stream/track/${trackId}`,
|
||||||
})
|
})
|
||||||
.href(),
|
.href(),
|
||||||
httpHeaders: {
|
httpHeaders: [
|
||||||
httpHeader: [
|
{
|
||||||
{
|
httpHeader: [{
|
||||||
header: "Authorization",
|
header: "bnbt",
|
||||||
value: `Bearer ${smapiTokenAsString(smapiAuthToken)}`,
|
value: smapiAuthToken.token,
|
||||||
},
|
}],
|
||||||
],
|
},
|
||||||
},
|
{
|
||||||
|
httpHeader: [{
|
||||||
|
header: "bnbk",
|
||||||
|
value: smapiAuthToken.key,
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(musicService.login).toHaveBeenCalledWith(serviceToken);
|
expect(musicService.login).toHaveBeenCalledWith(serviceToken);
|
||||||
|
|||||||
Reference in New Issue
Block a user