mirror of
https://github.com/teamhanko/hanko.git
synced 2025-10-27 14:17:56 +08:00
* Add endpoint to invalidate HTTP-Only cookie from the backend * Add methods to the UserClient SDK for logout * Remove session token fetch and add unit test for logout * Update public router to use JWT middleware * Add logout button to frontend. Route back to login page once logout is successful. * Add a logout failur event * Update logout logic in SDK * Remove unneeded endpoint from main.go * Update logoutlink reference * Fix request path; undo change in package order * Update common.css to incldue hanko-logout * feat(fronend-sdk): remove cookie during cross-domain operations * fix(frontend-sdk): No unauthorized error during logout, when the user is already logged out * feat(backend): Create an audit log entry when the user logs off * chore(frontend-sdk): re-generate jsdoc * fix: Adjust logout response codes and the corresponding frontend sdk error handling * chore(frontend-sdk): re-generate jsdoc * feat: add logout endpoint specification to the docs * Fix broken unit test * Remove logout button from elements * Add event listener on frontend to call the logout method from SDK * Rollback changes to SecuredContent on e2e tests * Update logout test on user * Update quickstart/public/assets/css/common.css Co-authored-by: bjoern-m <56024829+bjoern-m@users.noreply.github.com> --------- Co-authored-by: Björn Müller <bjoern.mueller@hanko.io> Co-authored-by: bjoern-m <56024829+bjoern-m@users.noreply.github.com>
221 lines
6.6 KiB
TypeScript
221 lines
6.6 KiB
TypeScript
import {
|
|
ConflictError,
|
|
NotFoundError,
|
|
TechnicalError,
|
|
UserClient,
|
|
} from "../../../src";
|
|
import { Response } from "../../../src/lib/client/HttpClient";
|
|
|
|
const userID = "test-user-1";
|
|
const email = "test-email-1@test";
|
|
const credentials = [{ id: "test-credential-1" }];
|
|
|
|
let userClient: UserClient;
|
|
|
|
beforeEach(() => {
|
|
userClient = new UserClient("http://test.api");
|
|
});
|
|
|
|
describe("UserClient.getInfo()", () => {
|
|
it("should retrieve user info", async () => {
|
|
const response = new Response(new XMLHttpRequest());
|
|
response.ok = true;
|
|
response._decodedJSON = {
|
|
id: userID,
|
|
verified: true,
|
|
has_webauthn_credential: true,
|
|
};
|
|
|
|
jest.spyOn(userClient.client, "post").mockResolvedValueOnce(response);
|
|
const getInfoResponse = userClient.getInfo(email);
|
|
await expect(getInfoResponse).resolves.toBe(response._decodedJSON);
|
|
|
|
expect(userClient.client.post).toHaveBeenCalledWith("/user", {
|
|
email,
|
|
});
|
|
});
|
|
|
|
it("should throw error when user not found", async () => {
|
|
const response = new Response(new XMLHttpRequest());
|
|
response.status = 404;
|
|
jest.spyOn(userClient.client, "post").mockResolvedValue(response);
|
|
|
|
const user = userClient.getInfo(email);
|
|
await expect(user).rejects.toThrow(NotFoundError);
|
|
});
|
|
|
|
it("should throw error when API response is not ok", async () => {
|
|
const response = new Response(new XMLHttpRequest());
|
|
userClient.client.post = jest.fn().mockResolvedValue(response);
|
|
|
|
const user = userClient.getInfo(email);
|
|
await expect(user).rejects.toThrowError(TechnicalError);
|
|
});
|
|
|
|
it("should throw error on API communication failure", async () => {
|
|
userClient.client.post = jest
|
|
.fn()
|
|
.mockRejectedValue(new Error("Test error"));
|
|
|
|
const user = userClient.getInfo(email);
|
|
await expect(user).rejects.toThrowError("Test error");
|
|
});
|
|
});
|
|
|
|
describe("UserClient.getCurrent()", () => {
|
|
it("should retrieve currently logged in user", async () => {
|
|
const responseMe = new Response(new XMLHttpRequest());
|
|
responseMe.ok = true;
|
|
responseMe._decodedJSON = {
|
|
id: userID,
|
|
};
|
|
|
|
const responseUser = new Response(new XMLHttpRequest());
|
|
responseUser.ok = true;
|
|
responseUser._decodedJSON = {
|
|
id: userID,
|
|
email,
|
|
webauthn_credentials: credentials,
|
|
};
|
|
|
|
jest
|
|
.spyOn(userClient.client, "get")
|
|
.mockResolvedValueOnce(responseMe)
|
|
.mockResolvedValueOnce(responseUser);
|
|
|
|
const user = userClient.getCurrent();
|
|
await expect(user).resolves.toBe(responseUser._decodedJSON);
|
|
|
|
expect(userClient.client.get).toHaveBeenNthCalledWith(1, "/me");
|
|
expect(userClient.client.get).toHaveBeenNthCalledWith(
|
|
2,
|
|
`/users/${userID}`
|
|
);
|
|
});
|
|
|
|
it.each`
|
|
statusMe | statusUsers | error
|
|
${400} | ${200} | ${"Unauthorized error"}
|
|
${401} | ${200} | ${"Unauthorized error"}
|
|
${404} | ${200} | ${"Unauthorized error"}
|
|
${200} | ${400} | ${"Unauthorized error"}
|
|
${200} | ${401} | ${"Unauthorized error"}
|
|
${200} | ${404} | ${"Unauthorized error"}
|
|
${200} | ${500} | ${"Technical error"}
|
|
${500} | ${200} | ${"Technical error"}
|
|
`(
|
|
"should throw error if API returns an error status",
|
|
async ({ statusMe, statusUsers, error }) => {
|
|
const responseMe = new Response(new XMLHttpRequest());
|
|
responseMe.status = statusMe;
|
|
responseMe.ok = statusMe >= 200 && statusMe <= 299;
|
|
|
|
const responseUser = new Response(new XMLHttpRequest());
|
|
responseUser.status = statusUsers;
|
|
responseUser.ok = statusUsers >= 200 && statusUsers <= 299;
|
|
|
|
jest
|
|
.spyOn(userClient.client, "get")
|
|
.mockResolvedValueOnce(responseMe)
|
|
.mockResolvedValueOnce(responseUser);
|
|
|
|
const user = userClient.getCurrent();
|
|
await expect(user).rejects.toThrow(error);
|
|
}
|
|
);
|
|
|
|
it("should throw error on API communication failure", async () => {
|
|
userClient.client.get = jest
|
|
.fn()
|
|
.mockRejectedValue(new Error("Test error"));
|
|
|
|
const user = userClient.getCurrent();
|
|
await expect(user).rejects.toThrowError("Test error");
|
|
});
|
|
});
|
|
|
|
describe("UserClient.create()", () => {
|
|
it("should create a user", async () => {
|
|
const response = new Response(new XMLHttpRequest());
|
|
response.ok = true;
|
|
response._decodedJSON = {
|
|
id: userID,
|
|
email,
|
|
webauthn_credentials: credentials,
|
|
};
|
|
|
|
jest.spyOn(userClient.client, "post").mockResolvedValueOnce(response);
|
|
const getInfoResponse = userClient.create(email);
|
|
await expect(getInfoResponse).resolves.toBe(response._decodedJSON);
|
|
|
|
expect(userClient.client.post).toHaveBeenCalledWith("/users", {
|
|
email,
|
|
});
|
|
});
|
|
|
|
it("should throw error when user already exists", async () => {
|
|
const response = new Response(new XMLHttpRequest());
|
|
response.status = 409;
|
|
jest.spyOn(userClient.client, "post").mockResolvedValue(response);
|
|
|
|
const user = userClient.create(email);
|
|
await expect(user).rejects.toThrow(ConflictError);
|
|
});
|
|
|
|
it("should throw error if API response is not ok (no 2xx, no 4xx)", async () => {
|
|
const response = new Response(new XMLHttpRequest());
|
|
jest.spyOn(userClient.client, "post").mockResolvedValue(response);
|
|
|
|
const user = userClient.create(email);
|
|
await expect(user).rejects.toThrow(TechnicalError);
|
|
});
|
|
|
|
it("should throw error on API communication failure", async () => {
|
|
userClient.client.post = jest
|
|
.fn()
|
|
.mockRejectedValue(new Error("Test error"));
|
|
|
|
const user = userClient.create(email);
|
|
await expect(user).rejects.toThrowError("Test error");
|
|
});
|
|
});
|
|
|
|
describe("UserClient.logout()", () => {
|
|
it.each`
|
|
status
|
|
${200}
|
|
${401}
|
|
`("should return true if logout is successful", async ({ status }) => {
|
|
const response = new Response(new XMLHttpRequest());
|
|
response.status = status;
|
|
response.ok = status >= 200 && status <= 299;
|
|
|
|
jest.spyOn(userClient.client, "post").mockResolvedValueOnce(response);
|
|
await expect(userClient.logout()).resolves.not.toThrow();
|
|
|
|
expect(userClient.client.post).toHaveBeenCalledWith("/logout");
|
|
});
|
|
|
|
it.each`
|
|
status | error
|
|
${400} | ${"Technical error"}
|
|
${404} | ${"Technical error"}
|
|
${500} | ${"Technical error"}
|
|
`(
|
|
"should throw error if API returns an error status",
|
|
async ({ status, error }) => {
|
|
const response = new Response(new XMLHttpRequest());
|
|
response.status = status;
|
|
response.ok = status >= 200 && status <= 299;
|
|
|
|
jest
|
|
.spyOn(userClient.client, "post")
|
|
.mockResolvedValueOnce(response)
|
|
|
|
await expect(userClient.logout()).rejects.toThrow(error);
|
|
|
|
expect(userClient.client.post).toHaveBeenCalledWith("/logout");
|
|
}
|
|
);
|
|
});
|