switched to hono/jwt to save some kb

This commit is contained in:
dswbx
2024-11-25 17:54:26 +01:00
parent 16a6a3315d
commit 1c94777317
7 changed files with 49 additions and 41 deletions

View File

@@ -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)
};
}