mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
switched to hono/jwt to save some kb
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { decodeJwt, jwtVerify } from "jose";
|
||||
import { Authenticator, type User, type UserPool } from "../../src/auth";
|
||||
import { cookieConfig } from "../../src/auth/authenticate/Authenticator";
|
||||
import { PasswordStrategy } from "../../src/auth/authenticate/strategies/PasswordStrategy";
|
||||
|
||||
@@ -51,7 +51,6 @@
|
||||
"dayjs": "^1.11.13",
|
||||
"fast-xml-parser": "^4.4.0",
|
||||
"hono": "^4.6.12",
|
||||
"jose": "^5.6.3",
|
||||
"jotai": "^2.10.1",
|
||||
"kysely": "^0.27.4",
|
||||
"liquidjs": "^10.15.0",
|
||||
@@ -100,9 +99,6 @@
|
||||
"splitting": false,
|
||||
"loader": {
|
||||
".svg": "dataurl"
|
||||
},
|
||||
"esbuild": {
|
||||
"drop": ["console", "debugger"]
|
||||
}
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AuthApi } from "auth/api/AuthApi";
|
||||
import { DataApi } from "data/api/DataApi";
|
||||
import { decodeJwt } from "jose";
|
||||
import { decode } from "hono/jwt";
|
||||
import { MediaApi } from "media/api/MediaApi";
|
||||
import { SystemApi } from "modules/SystemApi";
|
||||
|
||||
@@ -51,7 +51,7 @@ export class Api {
|
||||
const token = localStorage.getItem(this.tokenKey);
|
||||
if (token) {
|
||||
this.token = token;
|
||||
this.user = decodeJwt(token) as any;
|
||||
this.user = decode(token).payload as any;
|
||||
}
|
||||
} else {
|
||||
if (typeof window !== "undefined" && "__BKND__" in window) {
|
||||
@@ -63,7 +63,7 @@ export class Api {
|
||||
|
||||
updateToken(token?: string, rebuild?: boolean) {
|
||||
this.token = token;
|
||||
this.user = token ? (decodeJwt(token) as any) : undefined;
|
||||
this.user = token ? (decode(token).payload as any) : undefined;
|
||||
|
||||
if (this.tokenStorage === "localStorage") {
|
||||
const key = this.tokenKey;
|
||||
|
||||
@@ -11,10 +11,12 @@ import {
|
||||
} from "core/utils";
|
||||
import type { Context, Hono } from "hono";
|
||||
import { deleteCookie, getSignedCookie, setSignedCookie } from "hono/cookie";
|
||||
import { decode, sign, verify } from "hono/jwt";
|
||||
import type { CookieOptions } from "hono/utils/cookie";
|
||||
import { type JWTVerifyOptions, SignJWT, jwtVerify } from "jose";
|
||||
import { omit } from "lodash-es";
|
||||
|
||||
type Input = any; // workaround
|
||||
export type JWTPayload = Parameters<typeof sign>[0];
|
||||
|
||||
// @todo: add schema to interface to ensure proper inference
|
||||
export interface Strategy {
|
||||
@@ -50,6 +52,7 @@ export interface UserPool<Fields = "id" | "email" | "username"> {
|
||||
create: (user: CreateUser) => Promise<User | undefined>;
|
||||
}
|
||||
|
||||
const defaultCookieExpires = 60 * 60 * 24 * 7; // 1 week in seconds
|
||||
export const cookieConfig = Type.Partial(
|
||||
Type.Object({
|
||||
renew: Type.Boolean({ default: true }),
|
||||
@@ -57,7 +60,7 @@ export const cookieConfig = Type.Partial(
|
||||
sameSite: StringEnum(["strict", "lax", "none"], { default: "lax" }),
|
||||
secure: Type.Boolean({ default: true }),
|
||||
httpOnly: Type.Boolean({ default: true }),
|
||||
expires: Type.Number({ default: 168 })
|
||||
expires: Type.Number({ default: defaultCookieExpires }) // seconds
|
||||
}),
|
||||
{ default: {}, additionalProperties: false }
|
||||
);
|
||||
@@ -66,8 +69,8 @@ export const jwtConfig = Type.Object(
|
||||
{
|
||||
// @todo: autogenerate a secret if not present. But it must be persisted from AppAuth
|
||||
secret: Type.String({ default: "" }),
|
||||
alg: Type.Optional(Type.String({ enum: ["HS256"], default: "HS256" })),
|
||||
expiresIn: Type.Optional(Type.String()),
|
||||
alg: Type.Optional(StringEnum(["HS256", "HS384", "HS512"], { default: "HS256" })),
|
||||
expires: Type.Optional(Type.Number()), // seconds
|
||||
issuer: Type.Optional(Type.String()),
|
||||
fields: Type.Array(Type.String(), { default: ["id", "email", "role"] })
|
||||
},
|
||||
@@ -157,56 +160,55 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
|
||||
}
|
||||
}
|
||||
|
||||
const jwt = new SignJWT(user)
|
||||
.setProtectedHeader({ alg: this.config.jwt?.alg ?? "HS256" })
|
||||
.setIssuedAt();
|
||||
const payload: JWTPayload = {
|
||||
...user,
|
||||
iat: Math.floor(Date.now() / 1000)
|
||||
};
|
||||
|
||||
// issuer
|
||||
if (this.config.jwt?.issuer) {
|
||||
jwt.setIssuer(this.config.jwt.issuer);
|
||||
payload.iss = this.config.jwt.issuer;
|
||||
}
|
||||
|
||||
if (this.config.jwt?.expiresIn) {
|
||||
jwt.setExpirationTime(this.config.jwt.expiresIn);
|
||||
// expires in seconds
|
||||
if (this.config.jwt?.expires) {
|
||||
payload.exp = Math.floor(Date.now() / 1000) + this.config.jwt.expires;
|
||||
}
|
||||
|
||||
return jwt.sign(new TextEncoder().encode(this.config.jwt?.secret ?? ""));
|
||||
return sign(payload, this.config.jwt?.secret ?? "", this.config.jwt?.alg ?? "HS256");
|
||||
}
|
||||
|
||||
async verify(jwt: string): Promise<boolean> {
|
||||
const options: JWTVerifyOptions = {
|
||||
algorithms: [this.config.jwt?.alg ?? "HS256"]
|
||||
};
|
||||
|
||||
if (this.config.jwt?.issuer) {
|
||||
options.issuer = this.config.jwt.issuer;
|
||||
}
|
||||
|
||||
if (this.config.jwt?.expiresIn) {
|
||||
options.maxTokenAge = this.config.jwt.expiresIn;
|
||||
}
|
||||
|
||||
try {
|
||||
const { payload } = await jwtVerify<User>(
|
||||
const payload = await verify(
|
||||
jwt,
|
||||
new TextEncoder().encode(this.config.jwt?.secret ?? ""),
|
||||
options
|
||||
this.config.jwt?.secret ?? "",
|
||||
this.config.jwt?.alg ?? "HS256"
|
||||
);
|
||||
this._user = payload;
|
||||
|
||||
// manually verify issuer (hono doesn't support it)
|
||||
if (this.config.jwt?.issuer) {
|
||||
if (payload.iss !== this.config.jwt.issuer) {
|
||||
throw new Exception("Invalid issuer", 403);
|
||||
}
|
||||
}
|
||||
|
||||
this._user = omit(payload, ["iat", "exp", "iss"]) as SafeUser;
|
||||
return true;
|
||||
} catch (e) {
|
||||
this._user = undefined;
|
||||
//console.error(e);
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private get cookieOptions(): CookieOptions {
|
||||
const { expires = 168, renew, ...cookieConfig } = this.config.cookie;
|
||||
const { expires = defaultCookieExpires, renew, ...cookieConfig } = this.config.cookie;
|
||||
|
||||
return {
|
||||
...cookieConfig,
|
||||
expires: new Date(Date.now() + expires * 60 * 60 * 1000)
|
||||
expires: new Date(Date.now() + expires * 1000)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,15 @@ export const migrations: Migration[] = [
|
||||
version: 7,
|
||||
up: async (config, { db }) => {
|
||||
// automatically adds auth.cookie options
|
||||
return config;
|
||||
// remove "expiresIn" (string), it's now "expires" (number)
|
||||
const { expiresIn, ...jwt } = config.auth.jwt;
|
||||
return {
|
||||
...config,
|
||||
auth: {
|
||||
...config.auth,
|
||||
jwt
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -8,17 +8,20 @@ const result = await esbuild.build({
|
||||
conditions: ["worker", "browser"],
|
||||
entryPoints: ["./src/index.ts"],
|
||||
outdir: "dist",
|
||||
external: [],
|
||||
external: ["__STATIC_CONTENT_MANIFEST", "cloudflare:workers"],
|
||||
format: "esm",
|
||||
target: "es2022",
|
||||
keepNames: true,
|
||||
bundle: true,
|
||||
metafile: true,
|
||||
minify: true,
|
||||
loader: {
|
||||
".html": "copy"
|
||||
},
|
||||
define: {
|
||||
IS_CLOUDFLARE_WORKER: "true"
|
||||
}
|
||||
});
|
||||
|
||||
await Bun.write("dist/meta.json", JSON.stringify(result.metafile));
|
||||
//console.log("result", result.metafile);
|
||||
await $`gzip dist/index.js -c > dist/index.js.gz`;
|
||||
|
||||
Reference in New Issue
Block a user