Skip to content

feat: Optimize JWT payload #16

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 27 additions & 13 deletions packages/flyyer-lite/src/flyyer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,24 +86,38 @@ export class Flyyer<T extends FlyyerVariables = FlyyerVariables> implements Flyy
public sign(
project: FlyyerParams<T>["project"],
path: string, // normalized
params: string,
params: string | Record<any, any>,
strategy: FlyyerParams<T>["strategy"],
secret: FlyyerParams<T>["secret"],
): string | undefined | void {}

public params(extra?: any, options?: IStringifyOptions): string {
public params(extra?: any, options?: IStringifyOptions): string | Record<any, any> {
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);
}
}

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/flyyer/src/flyyer-signed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class Flyyer<T extends FlyyerVariables = FlyyerVariables> extends FlyyerB
public static sign<T>(
project: FlyyerParams<T>["project"],
path: string, // normalized
params: string,
params: string | Record<any, any>,
strategy: FlyyerParams<T>["strategy"],
secret: FlyyerParams<T>["secret"],
): string | undefined {
Expand All @@ -31,7 +31,7 @@ export class Flyyer<T extends FlyyerVariables = FlyyerVariables> 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`.");
}
Expand Down
2 changes: 1 addition & 1 deletion packages/flyyer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
14 changes: 14 additions & 0 deletions packages/flyyer/src/jwt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
132 changes: 105 additions & 27 deletions packages/flyyer/test/flyyer.test.ts
Original file line number Diff line number Diff line change
@@ -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", () => {
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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");
});
});