import express, { Request } from "express"; import uuid from "uuid"; import supertest from "supertest"; import crypto from "crypto"; import { Gateman } from "../src"; import { redisClient } from "../mocks"; const app = express(); const mockGatemanConfig = { authScheme: "Darth", redis: redisClient, secret: crypto.randomBytes(16).toString("hex"), }; const createMockGateman = (service?: string) => new Gateman({ ...mockGatemanConfig, service: service || "mock-service", }); const appGateman = createMockGateman(); const routeHandler = async (req: Request, res) => { //@ts-ignore res.send(req.user); }; const decodeDataRouteHandler = async (req: Request, res) => { //@ts-ignore res.json(req.data); }; /** * Routes and handles for mock app server */ app.get("/default", appGateman.guard(), routeHandler); app.get("/user", appGateman.guard("user"), routeHandler); app.get("/admin", appGateman.guard("admin"), routeHandler); app.get("/agent", appGateman.guard("agent"), routeHandler); app.get("/service", appGateman.guard("service", "budgets"), routeHandler); app.get( "/multiple-services", appGateman.guard("service", ["users", "cards"]), routeHandler ); app.get( "/user-and-service", appGateman.guard(["user", "service"], "wallets"), routeHandler ); app.get( "/user-and-multiple-services", appGateman.guard(["user", "service"], ["transactions", "trips"]), routeHandler ); app.get("/decode-data", appGateman.guard(), decodeDataRouteHandler); const request = supertest(app); afterAll(async () => { await redisClient.quit(); }); describe("token creation", () => { it("creates an encrypted session token", async () => { const gateman = createMockGateman(); const id = uuid(); const token = await gateman.createSession({ id }); const data = await gateman.decrypt(token); expect(typeof token).toBe("string"); expect(data.id).toBe(id); expect(data.role).toBe("user"); }); it("creates a headless token", async () => { const gateman = createMockGateman("user"); const id = uuid(); const service = "user"; const token = await gateman.createHeadlessToken({ id }); const data = await gateman.decrypt(token); expect(typeof token).toBe("string"); expect(data.id).toBe(id); expect(data.role).toBe("service"); expect(data.service).toBe(service); }); }); it("it encodes and decodes an object in a token", async () => { const gateman = createMockGateman(); const data = { name: uuid(), age: uuid(), }; const token = await gateman.createSession({ id: uuid(), data }); const { body } = await request .get("/decode-data") .set("Authorization", `Bearer ${token}`); expect(body.name).toBe(data.name); expect(body.age).toBe(data.age); }); describe("Header management", () => { it("throws an error if the authorization header is missing", async () => { const { body } = await request.get("/default"); expect(body.status).toBe("error"); expect(body.message).toBe("Required Authorization header not found"); }); it("throws an error if the token is missing in the authorization header", async () => { const { body } = await request .get("/default") .set("Authorization", "Bearer"); expect(body.status).toBe("error"); expect(body.message).toBe("Token not specified in Authorization header"); }); it("throws an error if an invalid auth scheme is provided in the authorization header", async () => { const gateman = createMockGateman(); const token = await gateman.createSession({ id: uuid() }); const { body } = await request .get("/default") .set("Authorization", `Kapaichumarimarichopaco ${token}`); expect(body.status).toBe("error"); expect(body.message).toBe("Invalid auth scheme provided"); }); }); describe("session management", () => { it("gives access to a logged in user, agent or admin", async () => { const gateman = createMockGateman(); const userId = uuid(); const adminId = uuid(); const agentId = uuid(); const userToken = await gateman.createSession({ id: userId, role: "user" }); const adminToken = await gateman.createSession({ id: adminId, role: "admin", }); const agentToken = await gateman.createSession({ id: agentId, role: "agent", }); const userResponse = await request .get("/user") .set("Authorization", `Bearer ${userToken}`); const adminResponse = await request .get("/admin") .set("Authorization", `Bearer ${adminToken}`); const agentResponse = await request .get("/agent") .set("Authorization", `Bearer ${agentToken}`); expect(userResponse.text).toBe(userId); expect(adminResponse.text).toBe(adminId); expect(agentResponse.text).toBe(agentId); }); it("clears a session", async () => { const gateman = createMockGateman(); const id = uuid(); const token = await gateman.createSession({ id }); const response = await request .get("/user") .set("Authorization", `Bearer ${token}`); expect(response.text).toBe(id); await gateman.clearSession(id); const { body } = await request .get("/user") .set("Authorization", `Bearer ${token}`); expect(body.status).toBe("error"); expect(body.message).toBe("Invalid session token"); }); it("prevents granting access with an expired token", async () => { const gateman = createMockGateman(); const id = uuid(); const token = await gateman.createSession({ id }); await gateman.createSession({ id }); const { body } = await request .get("/user") .set("Authorization", `Bearer ${token}`); expect(body.status).toBe("error"); expect(body.message).toBe("Expired session token"); }); }); describe("user role guard", () => { it("uses the user role guard by default when it is called without arguments", async () => { const gateman = createMockGateman(); const id = uuid(); const token = await gateman.createSession({ id }); const { text } = await request .get("/default") .set("Authorization", `Bearer ${token}`); expect(text).toBe(id); }); it("uses the user role guard for authentication", async () => { const gateman = createMockGateman(); const id = uuid(); const token = await gateman.createSession({ id }); const { text } = await request .get("/user") .set("Authorization", `Bearer ${token}`); expect(text).toBe(id); }); it("it throws an error when an invalid role is provided", async () => { const gateman = createMockGateman("wallet"); const adminToken = await gateman.createSession({ id: uuid(), role: "admin", }); const agentToken = await gateman.createSession({ id: uuid(), role: "agent", }); const serviceToken = await gateman.createHeadlessToken(uuid()); const adminResponse = await request .get("/user") .set("Authorization", `Bearer ${adminToken}`); const agentResponse = await request .get("/user") .set("Authorization", `Bearer ${agentToken}`); const serviceResponse = await request .get("/user") .set("Authorization", `Darth ${serviceToken}`); const serviceWithBearerSchemeResponse = await request .get("/user") .set("Authorization", `Bearer ${serviceToken}`); expect(adminResponse.body.message).toBe( "You do not have permission to call this endpoint" ); expect(agentResponse.body.message).toBe( "You do not have permission to call this endpoint" ); expect(serviceResponse.body.message).toBe("Invalid auth scheme provided"); expect(serviceWithBearerSchemeResponse.body.message).toBe( "You do not have permission to call this endpoint" ); }); }); describe("admin role guard", () => { it("uses the admin role guard for authentication", async () => { const gateman = createMockGateman(); const id = uuid(); const token = await gateman.createSession({ id, role: "admin" }); const { text } = await request .get("/admin") .set("Authorization", `Bearer ${token}`); expect(text).toBe(id); }); it("throws an error when an invalid role is provided for an admin role guard", async () => { const gateman = createMockGateman("remittance"); const userToken = await gateman.createSession({ id: uuid() }); const serviceToken = await gateman.createHeadlessToken({ id: uuid() }); const userResponse = await request .get("/admin") .set("Authorization", `Bearer ${userToken}`); const serviceResponse = await request .get("/admin") .set("Authorization", `Darth ${serviceToken}`); const serviceWithBearerSchemeResponse = await request .get("/admin") .set("Authorization", `Bearer ${serviceToken}`); expect(userResponse.body.message).toBe( "You do not have permission to call this endpoint" ); expect(serviceResponse.body.message).toBe("Invalid auth scheme provided"); expect(serviceWithBearerSchemeResponse.body.message).toBe( "You do not have permission to call this endpoint" ); }); }); describe("agent role guard", () => { it("uses the agent role guard for authentication", async () => { const gateman = createMockGateman(); const id = uuid(); const token = await gateman.createSession({ id, role: "agent" }); const { text } = await request .get("/agent") .set("Authorization", `Bearer ${token}`); expect(text).toBe(id); }); it("throws an error when an invalid role is provided for an agent role guard", async () => { const gateman = createMockGateman("remittance"); const userToken = await gateman.createSession({ id: uuid() }); const serviceToken = await gateman.createHeadlessToken({ id: uuid() }); const userResponse = await request .get("/agent") .set("Authorization", `Bearer ${userToken}`); const serviceResponse = await request .get("/agent") .set("Authorization", `Darth ${serviceToken}`); const serviceWithBearerSchemeResponse = await request .get("/agent") .set("Authorization", `Bearer ${serviceToken}`); expect(userResponse.body.message).toBe( "You do not have permission to call this endpoint" ); expect(serviceResponse.body.message).toBe("Invalid auth scheme provided"); expect(serviceWithBearerSchemeResponse.body.message).toBe( "You do not have permission to call this endpoint" ); }); }); describe("service role guard", () => { it("uses the service role guard for authentication", async () => { const gateman = createMockGateman("budgets"); const id = uuid(); const token = await gateman.createHeadlessToken({ id }); const { text } = await request .get("/service") .set("Authorization", `Darth ${token}`); expect(text).toBe(id); }); it("case sensitive for auth scheme", async () => { const gateman = createMockGateman("budgets"); const id = uuid(); const token = await gateman.createHeadlessToken({ id }); const authSchemeVariations = ["darth", "dArth", "DARTH", "darTH"]; for (const authScheme of authSchemeVariations) { const { body } = await request .get("/service") .set("Authorization", `${authScheme} ${token}`); expect(body.message).toBe("Invalid auth scheme provided"); } }); it("throws an error if an invalid auth scheme is used for a service role guard", async () => { const gateman = createMockGateman("budgets"); const id = uuid(); const token = await gateman.createHeadlessToken({ id }); const { body } = await request .get("/service") .set("Authorization", `Bearer ${token}`); expect(body.status).toBe("error"); expect(body.message).toBe("Invalid auth scheme provided"); }); it("throws an error if an unrecognized service calls a service role guard", async () => { const gateman = createMockGateman("wallet"); const token = await gateman.createHeadlessToken({ id: uuid() }); const { body } = await request .get("/service") .set("Authorization", `Darth ${token}`); expect(body.status).toBe("error"); expect(body.message).toBe( "The service wallet does not have permission to call this endpoint" ); }); it("throws an error when an admin, agent or user calls a service role guard", async () => { const gateman = createMockGateman(); const userToken = await gateman.createSession({ id: uuid(), role: "user" }); const adminToken = await gateman.createSession({ id: uuid(), role: "admin", }); const agentToken = await gateman.createSession({ id: uuid(), role: "agent", }); const userResponse = await request .get("/service") .set("Authorization", `Bearer ${userToken}`); const adminResponse = await request .get("/service") .set("Authorization", `Bearer ${adminToken}`); const agentResponse = await request .get("/service") .set("Authorization", `Bearer ${agentToken}`); expect(userResponse.body.message).toBe("Invalid auth scheme provided"); expect(adminResponse.body.message).toBe("Invalid auth scheme provided"); expect(agentResponse.body.message).toBe("Invalid auth scheme provided"); }); it("allows multiple services call a service role guard", async () => { const usersServiceId = uuid(); const cardsServiceId = uuid(); const usersServiceToken = await createMockGateman( "users" ).createHeadlessToken({ id: usersServiceId }); const cardsServiceToken = await createMockGateman( "cards" ).createHeadlessToken({ id: cardsServiceId }); const budgetsServiceToken = await createMockGateman( "budgets" ).createHeadlessToken({ id: uuid() }); const userServiceResponse = await request .get("/multiple-services") .set("Authorization", `Darth ${usersServiceToken}`); const cardsServiceResponse = await request .get("/multiple-services") .set("Authorization", `Darth ${cardsServiceToken}`); const budgetsServiceResponse = await request .get("/multiple-services") .set("Authorization", `Darth ${budgetsServiceToken}`); expect(userServiceResponse.text).toBe(usersServiceId); expect(cardsServiceResponse.text).toBe(cardsServiceId); expect(budgetsServiceResponse.body.status).toBe("error"); expect(budgetsServiceResponse.body.message).toBe( "The service budgets does not have permission to call this endpoint" ); }); }); describe("user and a service role guard", () => { it("allows users and a service call an endpoint", async () => { const gateman = createMockGateman("wallets"); const userId = uuid(); const serviceId = uuid(); const userToken = await gateman.createSession({ id: userId }); const serviceToken = await gateman.createHeadlessToken({ id: serviceId }); const userResponse = await request .get("/user-and-service") .set("Authorization", `Bearer ${userToken}`); const serviceResponse = await request .get("/user-and-service") .set("Authorization", `Darth ${serviceToken}`); expect(userResponse.text).toBe(userId); expect(serviceResponse.text).toBe(serviceId); }); it("throws an error if an unrecognized service calls an endpoint", async () => { const gateman = createMockGateman("bants"); const token = await gateman.createHeadlessToken({ id: uuid() }); const { body } = await request .get("/user-and-service") .set("Authorization", `Darth ${token}`); expect(body.status).toBe("error"); expect(body.message).toBe( "The service bants does not have permission to call this endpoint" ); }); }); describe("user and multiple services", () => { it("allows users and multiple services call an endpoint", async () => { const gateman = createMockGateman(); const userId = uuid(); const transactionsServiceId = uuid(); const tripsServiceId = uuid(); const userToken = await gateman.createSession({ id: userId }); const transactionsServiceToken = await createMockGateman( "transactions" ).createHeadlessToken({ id: transactionsServiceId }); const tripsServiceToken = await createMockGateman( "trips" ).createHeadlessToken({ id: tripsServiceId }); const userResponse = await request .get("/user-and-multiple-services") .set("Authorization", `Bearer ${userToken}`); const transactionsServiceResponse = await request .get("/user-and-multiple-services") .set("Authorization", `Darth ${transactionsServiceToken}`); const tripsServiceResponse = await request .get("/user-and-multiple-services") .set("Authorization", `Darth ${tripsServiceToken}`); expect(userResponse.text).toBe(userId); expect(transactionsServiceResponse.text).toBe(transactionsServiceId); expect(tripsServiceResponse.text).toBe(tripsServiceId); }); it("throws an error if an unrecognized service calls an endpoint", async () => { const gateman = createMockGateman("indicud"); const token = await gateman.createHeadlessToken({ id: uuid() }); const { body } = await request .get("/user-and-multiple-services") .set("Authorization", `Darth ${token}`); expect(body.status).toBe("error"); expect(body.message).toBe( "The service indicud does not have permission to call this endpoint" ); }); });