diff --git a/packages/flyyer-lite/src/flyyer.ts b/packages/flyyer-lite/src/flyyer.ts index 5b2d250..a585bf6 100644 --- a/packages/flyyer-lite/src/flyyer.ts +++ b/packages/flyyer-lite/src/flyyer.ts @@ -86,24 +86,38 @@ export class Flyyer implements Flyy public sign( project: FlyyerParams["project"], path: string, // normalized - params: string, + params: string | Record, strategy: FlyyerParams["strategy"], secret: FlyyerParams["secret"], ): string | undefined | void {} - public params(extra?: any, options?: IStringifyOptions): string { + public params(extra?: any, options?: IStringifyOptions): string | Record { const meta = this.meta; - const defaults = { - __v: __V(meta.v), - __id: meta.id, - _w: meta.width, - _h: meta.height, - _res: meta.resolution, - _ua: meta.agent, - _def: this.default, - _ext: this.extension, - }; - return toQuery(Object.assign(defaults, this.variables, extra), options); + if (this.strategy && this.strategy.toLowerCase() === "jwt") { + const jwtDefaults = { + i: meta.id, + w: meta.width, + h: meta.height, + r: meta.resolution, + u: meta.agent, + e: this.extension, + def: this.default, + var: Object.assign(this.variables, extra), + }; + return jwtDefaults; + } else { + const defaults = { + __v: __V(meta.v), + __id: meta.id, + _w: meta.width, + _h: meta.height, + _res: meta.resolution, + _ua: meta.agent, + _def: this.default, + _ext: this.extension, + }; + return toQuery(Object.assign(defaults, this.variables, extra), options); + } } /** diff --git a/packages/flyyer/src/flyyer-signed.ts b/packages/flyyer/src/flyyer-signed.ts index 420f41a..ea9a342 100644 --- a/packages/flyyer/src/flyyer-signed.ts +++ b/packages/flyyer/src/flyyer-signed.ts @@ -14,7 +14,7 @@ export class Flyyer extends FlyyerB public static sign( project: FlyyerParams["project"], path: string, // normalized - params: string, + params: string | Record, strategy: FlyyerParams["strategy"], secret: FlyyerParams["secret"], ): string | undefined { @@ -31,7 +31,7 @@ export class Flyyer extends FlyyerB return Flyyer.signHMAC(data, secret); } if (str === "JWT") { - return Flyyer.signJWT({ path, params }, secret); + return Flyyer.signJWT({ path: path.startsWith("/") ? path : `/${path}`, params }, secret); } invariant(false, "Invalid `strategy`. Valid options are `HMAC` or `JWT`."); } diff --git a/packages/flyyer/src/index.ts b/packages/flyyer/src/index.ts index 8edaa28..2d84380 100644 --- a/packages/flyyer/src/index.ts +++ b/packages/flyyer/src/index.ts @@ -8,7 +8,7 @@ export { normalizePath, FlyyerPath } from "@flyyer/flyyer-lite"; export { FlyyerParams } from "@flyyer/flyyer-lite"; export { FlyyerRenderParams } from "@flyyer/flyyer-lite"; -export { CREATE_JWT_TOKEN, SIGN_JWT_TOKEN, SIGN_HMAC_DATA, BASE64_URL } from "./jwt"; +export { CREATE_JWT_TOKEN, SIGN_JWT_TOKEN, SIGN_HMAC_DATA, BASE64_URL, DECODE_JWT_TOKEN } from "./jwt"; export { Flyyer } from "./flyyer-signed"; export { FlyyerRender } from "./flyyer-render-signed"; export { isEqualFlyyer, isEqualFlyyerMeta, isEqualFlyyerRender } from "./compare"; diff --git a/packages/flyyer/src/jwt.ts b/packages/flyyer/src/jwt.ts index 4cc4fc9..27b7a80 100644 --- a/packages/flyyer/src/jwt.ts +++ b/packages/flyyer/src/jwt.ts @@ -45,3 +45,17 @@ export function SIGN_JWT_TOKEN(token: string, secret: string): string { export function SIGN_HMAC_DATA(data: string, secret: string): string { return HmacSHA256(data, secret).toString(); } + +export function DECODE_JWT_TOKEN(token: string): any { + const [encodedHeader, encodedData, signature] = token.split("."); + if (!encodedHeader || !encodedData || !signature) { + throw new Error("Invalid JWT token"); + } + let data: any; + try { + data = JSON.parse(Buffer.from(encodedData, "base64").toString()); + } catch (err) { + throw new Error("Invalid JWT token"); + } + return data; +} diff --git a/packages/flyyer/test/flyyer.test.ts b/packages/flyyer/test/flyyer.test.ts index eec8da2..dcb81a0 100644 --- a/packages/flyyer/test/flyyer.test.ts +++ b/packages/flyyer/test/flyyer.test.ts @@ -1,6 +1,6 @@ import { dequal } from "dequal/lite"; -import { Flyyer, isEqualFlyyer } from "../dist/index"; +import { Flyyer, isEqualFlyyer, DECODE_JWT_TOKEN } from "../dist/index"; describe("Flyyer", () => { it("Flyyer is instantiable", () => { @@ -92,32 +92,6 @@ describe("Flyyer", () => { expect(flyyer2.href()).toMatch(regex); }); - it("encodes url with JWT signature", () => { - const flyyer1 = new Flyyer({ - project: "project", - path: "/products/1", - secret: "sg1j0HVy9bsMihJqa8Qwu8ZYgCYHG0tx", - strategy: "JWT", - meta: { v: null }, - }); - expect(flyyer1.href()).toEqual( - "https://cdn.flyyer.io/v2/project/jwt-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXRoIjoicHJvZHVjdHMvMSIsInBhcmFtcyI6IiJ9.KMAG3_NQkfou6rkBc3gYunVilfqNnFdVzKd2IrRmUz4", - ); - }); - - it("sets 'default' image as '_def' param in JWT", () => { - const flyyer0 = new Flyyer({ - project: "project", - path: "path", - default: "/static/product/1.png", - secret: "sg1j0HVy9bsMihJqa8Qwu8ZYgCYHG0tx", - strategy: "JWT", - meta: { v: null }, - }); - expect(flyyer0.href()).toEqual( - "https://cdn.flyyer.io/v2/project/jwt-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXRoIjoicGF0aCIsInBhcmFtcyI6Il9kZWY9JTJGc3RhdGljJTJGcHJvZHVjdCUyRjEucG5nIn0.W-aMsd4jakMYftprBmOCFxdR67xMPKbdvLDgPLFv0Ws", - ); - }); it("sets 'default' image as '_def' param in HMAC", () => { const flyyer0 = new Flyyer({ project: "project", @@ -146,4 +120,108 @@ describe("Flyyer", () => { expect(isEqualFlyyer(flyyer0, flyyer1, dequal)).toEqual(true); expect(isEqualFlyyer(flyyer0, flyyer2, dequal)).toEqual(false); }); + + it("encodes url with JWT signature", () => { + const key = "sg1j0HVy9bsMihJqa8Qwu8ZYgCYHG0tx"; + const flyyer = new Flyyer({ + project: "project", + path: "products/1", + secret: key, + strategy: "JWT", + meta: { v: null }, + }); + const token = flyyer + .href() + .match(/(jwt-)(.*)(\??)/g)?.[0] + ?.slice(4); + const decoded = token ? DECODE_JWT_TOKEN(token) : ""; + expect(decoded["params"]).toEqual({ var: {} }); + expect(decoded["path"]).toEqual("/products/1"); + }); + + it("encodes url with JWT signature with variables", () => { + const key = "sg1j0HVy9bsMihJqa8Qwu8ZYgCYHG0tx"; + const flyyer = new Flyyer({ + project: "project", + path: "products/1", + secret: key, + strategy: "JWT", + variables: { title: "Hello world!" }, + }); + const token = flyyer + .href() + .match(/(jwt-)(.*)(\??)/g)?.[0] + ?.slice(4); + const decoded = token ? DECODE_JWT_TOKEN(token) : ""; + expect(decoded["params"]).toEqual({ var: { title: "Hello world!" } }); + expect(decoded["path"]).toEqual("/products/1"); + }); + + it("sets default image in JWT with relative URL", () => { + const key = "sg1j0HVy9bsMihJqa8Qwu8ZYgCYHG0tx"; + const flyyer0 = new Flyyer({ + project: "project", + path: "path", + default: "/static/product/1.png", + secret: key, + strategy: "JWT", + meta: { v: null }, + }); + + const token = flyyer0 + .href() + .match(/(jwt-)(.*)(\??)/g)?.[0] + ?.slice(4); + const decoded = token ? DECODE_JWT_TOKEN(token) : ""; + expect(decoded["params"]["def"]).toEqual("/static/product/1.png"); + }); + + it("sets default image in JWT with absolute URL", () => { + const key = "sg1j0HVy9bsMihJqa8Qwu8ZYgCYHG0tx"; + const flyyer0 = new Flyyer({ + project: "project", + path: "path", + default: "https://flyyer.io/static/product/1.png", + secret: key, + strategy: "JWT", + meta: { v: null }, + }); + + const token = flyyer0 + .href() + .match(/(jwt-)(.*)(\??)/g)?.[0] + ?.slice(4); + const decoded = token ? DECODE_JWT_TOKEN(token) : ""; + expect(decoded["params"]["def"]).toEqual("https://flyyer.io/static/product/1.png"); + expect(decoded["path"]).toEqual("/path"); + expect(decoded["params"]["var"]).toEqual({}); + }); + + it("encodes URL with JWT and has correct data", () => { + const key = "sg1j0HVy9bsMihJqa8Qwu8ZYgCYHG0tx"; + const flyyer0 = new Flyyer({ + project: "project", + path: "path/to/product", + default: "https://flyyer.io/static/product/1.png", + secret: key, + strategy: "JWT", + meta: { id: "h1", height: "200", width: 100, resolution: "90" }, + variables: { title: "Hello!" }, + }); + + const token = flyyer0 + .href() + .match(/(jwt-)(.*)(\??)/g)?.[0] + ?.slice(4); + const decoded = token ? DECODE_JWT_TOKEN(token) : ""; + expect(decoded["params"]).toEqual({ + i: "h1", + h: "200", + w: 100, + r: "90", + def: "https://flyyer.io/static/product/1.png", + var: { title: "Hello!" }, + }); + expect(decoded["path"]).toEqual("/path/to/product"); + }); });