mirror of
https://github.com/wkulhanek/bonob.git
synced 2025-12-21 17:33:29 +01:00
Refreshing bearer tokens when smapi token is refreshed (#85)
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { taskEither as TE } from "fp-ts";
|
||||
import { pipe } from "fp-ts/lib/function";
|
||||
|
||||
import { InMemoryMusicService } from "./in_memory_music_service";
|
||||
import {
|
||||
AuthSuccess,
|
||||
MusicLibrary,
|
||||
artistToArtistSummary,
|
||||
albumToAlbumSummary,
|
||||
@@ -28,7 +30,10 @@ describe("InMemoryMusicService", () => {
|
||||
|
||||
service.hasUser(credentials);
|
||||
|
||||
const token = (await service.generateToken(credentials)) as AuthSuccess;
|
||||
const token = await pipe(
|
||||
service.generateToken(credentials),
|
||||
TE.getOrElse(e => { throw e })
|
||||
)();
|
||||
|
||||
expect(token.userId).toEqual(credentials.username);
|
||||
expect(token.nickname).toEqual(credentials.username);
|
||||
@@ -43,7 +48,10 @@ describe("InMemoryMusicService", () => {
|
||||
|
||||
service.hasUser(credentials);
|
||||
|
||||
const token = (await service.generateToken(credentials)) as AuthSuccess;
|
||||
const token = await pipe(
|
||||
service.generateToken(credentials),
|
||||
TE.getOrElse(e => { throw e })
|
||||
)();
|
||||
|
||||
service.clear();
|
||||
|
||||
@@ -62,7 +70,11 @@ describe("InMemoryMusicService", () => {
|
||||
|
||||
service.hasUser(user);
|
||||
|
||||
const token = (await service.generateToken(user)) as AuthSuccess;
|
||||
const token = await pipe(
|
||||
service.generateToken(user),
|
||||
TE.getOrElse(e => { throw e })
|
||||
)();
|
||||
|
||||
musicLibrary = (await service.login(token.serviceToken)) as MusicLibrary;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { option as O } from "fp-ts";
|
||||
import { option as O, taskEither as TE } from "fp-ts";
|
||||
import * as A from "fp-ts/Array";
|
||||
import { fromEquals } from "fp-ts/lib/Eq";
|
||||
import { pipe } from "fp-ts/lib/function";
|
||||
@@ -34,23 +34,27 @@ export class InMemoryMusicService implements MusicService {
|
||||
generateToken({
|
||||
username,
|
||||
password,
|
||||
}: Credentials): Promise<AuthSuccess | AuthFailure> {
|
||||
}: Credentials): TE.TaskEither<AuthFailure, AuthSuccess> {
|
||||
if (
|
||||
username != undefined &&
|
||||
password != undefined &&
|
||||
this.users[username] == password
|
||||
) {
|
||||
return Promise.resolve({
|
||||
return TE.right({
|
||||
serviceToken: b64Encode(JSON.stringify({ username, password })),
|
||||
userId: username,
|
||||
nickname: username,
|
||||
type: "in-memory"
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve({ message: `Invalid user:${username}` });
|
||||
return TE.left(new AuthFailure(`Invalid user:${username}`));
|
||||
}
|
||||
}
|
||||
|
||||
refreshToken(serviceToken: string): TE.TaskEither<AuthFailure, AuthSuccess> {
|
||||
return this.generateToken(JSON.parse(b64Decode(serviceToken)))
|
||||
}
|
||||
|
||||
login(serviceToken: string): Promise<MusicLibrary> {
|
||||
const credentials = JSON.parse(b64Decode(serviceToken)) as Credentials;
|
||||
if (this.users[credentials.username] != credentials.password)
|
||||
|
||||
@@ -3,10 +3,10 @@ import dayjs from "dayjs";
|
||||
import request from "supertest";
|
||||
import Image from "image-js";
|
||||
import fs from "fs";
|
||||
import { either as E } from "fp-ts";
|
||||
import { either as E, taskEither as TE } from "fp-ts";
|
||||
import path from "path";
|
||||
|
||||
import { MusicService } from "../src/music_service";
|
||||
import { AuthFailure, MusicService } from "../src/music_service";
|
||||
import makeServer, {
|
||||
BONOB_ACCESS_TOKEN_HEADER,
|
||||
RangeBytesFromFilter,
|
||||
@@ -637,7 +637,7 @@ describe("server", () => {
|
||||
};
|
||||
|
||||
linkCodes.has.mockReturnValue(true);
|
||||
musicService.generateToken.mockResolvedValue(authSuccess);
|
||||
musicService.generateToken.mockReturnValue(TE.right(authSuccess))
|
||||
linkCodes.associate.mockReturnValue(true);
|
||||
|
||||
const res = await request(server)
|
||||
@@ -669,7 +669,7 @@ describe("server", () => {
|
||||
const message = `Invalid user:${username}`;
|
||||
|
||||
linkCodes.has.mockReturnValue(true);
|
||||
musicService.generateToken.mockResolvedValue({ message });
|
||||
musicService.generateToken.mockReturnValue(TE.left(new AuthFailure(message)))
|
||||
|
||||
const res = await request(server)
|
||||
.post(bonobUrl.append({ pathname: "/login" }).pathname())
|
||||
@@ -683,27 +683,6 @@ describe("server", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("when an unexpected failure occurs", () => {
|
||||
it("should return 403 with message", async () => {
|
||||
const username = "userDoesntExist";
|
||||
const password = "password";
|
||||
const linkCode = uuid();
|
||||
|
||||
linkCodes.has.mockReturnValue(true);
|
||||
musicService.generateToken.mockRejectedValue("BOOOOOOM");
|
||||
|
||||
const res = await request(server)
|
||||
.post(bonobUrl.append({ pathname: "/login" }).pathname())
|
||||
.set("accept-language", acceptLanguage)
|
||||
.type("form")
|
||||
.send({ username, password, linkCode })
|
||||
.expect(403);
|
||||
|
||||
expect(res.text).toContain(lang("loginFailed"));
|
||||
expect(res.text).toContain('Unexpected error occured - BOOOOOOM');
|
||||
});
|
||||
});
|
||||
|
||||
describe("when linkCode is invalid", () => {
|
||||
it("should return 400 with message", async () => {
|
||||
const username = "jane";
|
||||
@@ -777,7 +756,7 @@ describe("server", () => {
|
||||
|
||||
describe("when the Bearer token has expired", () => {
|
||||
it("should return a 401", async () => {
|
||||
smapiAuthTokens.verify.mockReturnValue(E.left(new ExpiredTokenError(serviceToken, 0)))
|
||||
smapiAuthTokens.verify.mockReturnValue(E.left(new ExpiredTokenError(serviceToken)))
|
||||
|
||||
const res = await request(server).head(
|
||||
bonobUrl
|
||||
@@ -865,7 +844,7 @@ describe("server", () => {
|
||||
|
||||
describe("when the Bearer token has expired", () => {
|
||||
it("should return a 401", async () => {
|
||||
smapiAuthTokens.verify.mockReturnValue(E.left(new ExpiredTokenError(serviceToken, 0)))
|
||||
smapiAuthTokens.verify.mockReturnValue(E.left(new ExpiredTokenError(serviceToken)))
|
||||
|
||||
const res = await request(server)
|
||||
.get(
|
||||
|
||||
@@ -2,7 +2,7 @@ import crypto from "crypto";
|
||||
import request from "supertest";
|
||||
import { Client, createClientAsync } from "soap";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import { either as E } from "fp-ts";
|
||||
import { either as E, taskEither as TE } from "fp-ts";
|
||||
import { DOMParserImpl } from "xmldom-ts";
|
||||
import * as xpath from "xpath-ts";
|
||||
import { randomInt } from "crypto";
|
||||
@@ -861,6 +861,7 @@ describe("defaultArtistArtURI", () => {
|
||||
describe("wsdl api", () => {
|
||||
const musicService = {
|
||||
generateToken: jest.fn(),
|
||||
refreshToken: jest.fn(),
|
||||
login: jest.fn(),
|
||||
};
|
||||
const linkCodes = {
|
||||
@@ -1079,10 +1080,11 @@ describe("wsdl api", () => {
|
||||
|
||||
describe("when token has expired", () => {
|
||||
it("should return a refreshed auth token", async () => {
|
||||
const oneDayAgo = clock.time.subtract(1, "d");
|
||||
const refreshedServiceToken = `refreshedServiceToken-${uuid()}`
|
||||
const newSmapiAuthToken = { token: `newToken-${uuid()}`, key: `newKey-${uuid()}` };
|
||||
|
||||
smapiAuthTokens.verify.mockReturnValue(E.left(new ExpiredTokenError(serviceToken, oneDayAgo.unix())));
|
||||
smapiAuthTokens.verify.mockReturnValue(E.left(new ExpiredTokenError(serviceToken)));
|
||||
musicService.refreshToken.mockReturnValue(TE.right({ serviceToken: refreshedServiceToken }));
|
||||
smapiAuthTokens.issue.mockReturnValue(newSmapiAuthToken);
|
||||
|
||||
const ws = await createClientAsync(`${service.uri}?wsdl`, {
|
||||
@@ -1101,6 +1103,9 @@ describe("wsdl api", () => {
|
||||
privateKey: newSmapiAuthToken.key,
|
||||
},
|
||||
});
|
||||
|
||||
expect(musicService.refreshToken).toHaveBeenCalledWith(serviceToken);
|
||||
expect(smapiAuthTokens.issue).toHaveBeenCalledWith(refreshedServiceToken);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1128,8 +1133,11 @@ describe("wsdl api", () => {
|
||||
|
||||
describe("when existing auth token has not expired", () => {
|
||||
it("should return a refreshed auth token", async () => {
|
||||
const refreshedServiceToken = `refreshedServiceToken-${uuid()}`
|
||||
const newSmapiAuthToken = { token: `newToken-${uuid()}`, key: `newKey-${uuid()}` };
|
||||
|
||||
smapiAuthTokens.verify.mockReturnValue(E.right(serviceToken));
|
||||
musicService.refreshToken.mockReturnValue(TE.right({ serviceToken: refreshedServiceToken }));
|
||||
smapiAuthTokens.issue.mockReturnValue(newSmapiAuthToken);
|
||||
|
||||
const ws = await createClientAsync(`${service.uri}?wsdl`, {
|
||||
@@ -1148,6 +1156,9 @@ describe("wsdl api", () => {
|
||||
privateKey: newSmapiAuthToken.key
|
||||
},
|
||||
});
|
||||
|
||||
expect(musicService.refreshToken).toHaveBeenCalledWith(serviceToken);
|
||||
expect(smapiAuthTokens.issue).toHaveBeenCalledWith(refreshedServiceToken);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1325,13 +1336,14 @@ describe("wsdl api", () => {
|
||||
|
||||
describe("when token has expired", () => {
|
||||
it("should return a fault of Client.TokenRefreshRequired with a refreshAuthTokenResult", async () => {
|
||||
const expiry = dayjs().subtract(1, "d");
|
||||
const refreshedServiceToken = `refreshedServiceToken-${uuid()}`
|
||||
const newToken = {
|
||||
token: `newToken-${uuid()}`,
|
||||
key: `newKey-${uuid()}`
|
||||
};
|
||||
|
||||
smapiAuthTokens.verify.mockReturnValue(E.left(new ExpiredTokenError(serviceToken, expiry.unix())))
|
||||
smapiAuthTokens.verify.mockReturnValue(E.left(new ExpiredTokenError(serviceToken)))
|
||||
musicService.refreshToken.mockReturnValue(TE.right({ serviceToken: refreshedServiceToken }))
|
||||
smapiAuthTokens.issue.mockReturnValue(newToken)
|
||||
musicService.login.mockRejectedValue(
|
||||
"fail, should not call login!"
|
||||
@@ -1360,7 +1372,8 @@ describe("wsdl api", () => {
|
||||
});
|
||||
|
||||
expect(smapiAuthTokens.verify).toHaveBeenCalledWith(smapiAuthToken);
|
||||
expect(smapiAuthTokens.issue).toHaveBeenCalledWith(serviceToken);
|
||||
expect(musicService.refreshToken).toHaveBeenCalledWith(serviceToken);
|
||||
expect(smapiAuthTokens.issue).toHaveBeenCalledWith(refreshedServiceToken);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -177,8 +177,7 @@ describe("auth", () => {
|
||||
expect(result).toEqual(
|
||||
E.left(
|
||||
new ExpiredTokenError(
|
||||
authToken,
|
||||
tokenIssuedAt.add(30, "seconds").unix()
|
||||
authToken
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user