feat(federation): Finalize Versia 0.6 port

This commit is contained in:
Jesse Wierzbinski 2026-03-31 04:13:16 +02:00
parent fca30b4dad
commit b7e77097ba
No known key found for this signature in database
20 changed files with 2788 additions and 439 deletions

565
bun.lock

File diff suppressed because it is too large Load diff

View file

@ -28,10 +28,7 @@ export const refetchUserCommand = defineCommand(
const spinner = ora("Refetching user").start();
try {
await User.fromVersia(
user.reference,
user.reference.domain as string,
);
await User.fromVersia(user.reference);
} catch (error) {
spinner.fail(
`Failed to refetch user ${chalk.gray(user.data.username)}`,

View file

@ -48,6 +48,7 @@
default = pkgs.mkShell rec {
libPath = with pkgs;
lib.makeLibraryPath [
vips
stdenv.cc.cc.lib
];
@ -55,7 +56,6 @@
buildInputs = with pkgs; [
bun
vips
nodePackages.typescript
nodePackages.typescript-language-server
nix-ld

View file

@ -87,7 +87,7 @@
"hono-openapi": "~1.1.2",
"hono-rate-limiter": "~0.5.1",
"html-to-text": "~9.0.5",
"ioredis": "~5.8.2",
"ioredis": "5.9.2",
"ip-matching": "~2.1.2",
"iso-639-1": "~3.1.5",
"linkify-html": "~4.3.2",
@ -158,6 +158,7 @@
"dependencies": {
"@bull-board/api": "catalog:",
"@bull-board/hono": "catalog:",
"@clerc/core": "catalog:",
"@clerc/plugin-completions": "catalog:",
"@clerc/plugin-friendly-error": "catalog:",
"@clerc/plugin-help": "catalog:",
@ -180,7 +181,6 @@
"blurhash": "catalog:",
"bullmq": "catalog:",
"chalk": "catalog:",
"@clerc/core": "catalog:",
"confbox": "catalog:",
"drizzle-orm": "catalog:",
"feed": "catalog:",
@ -216,5 +216,8 @@
"zod": "catalog:",
"zod-openapi": "catalog:",
"zod-validation-error": "catalog:"
},
"patchedDependencies": {
"bun-bagel@1.2.0": "patches/bun-bagel@1.2.0.patch"
}
}

View file

@ -50,10 +50,7 @@ export default apiRoute((app) =>
throw new ApiError(400, "Cannot refetch a local user");
}
const newUser = await User.fromVersia(
otherUser.reference,
otherUser.reference.domain as string,
);
const newUser = await User.fromVersia(otherUser.reference);
return context.json(newUser.toApi(false), 200);
},

View file

@ -106,10 +106,7 @@ export default apiRoute((app) =>
VersiaEntities.User,
);
const foundAccount = await User.fromVersia(
accountData,
instance.data.baseUrl,
);
const foundAccount = await User.fromVersia(accountData, instance);
return context.json(foundAccount.toApi(), 200);
},

View file

@ -101,7 +101,7 @@ export default apiRoute((app) =>
const foundAccount = await User.fromVersia(
accountData,
instance.data.baseUrl,
instance,
);
accounts.push(foundAccount);

View file

@ -200,7 +200,7 @@ export default apiRoute((app) =>
const newUser = await User.fromVersia(
accountData,
instance.data.baseUrl,
instance,
);
return context.json(

View file

@ -65,6 +65,7 @@ export default apiRoute((app) => {
const jwtPayload = (await verify(state, config.authentication.key, {
iss: config.http.base_url.toString(),
alg: "HS256",
})) as {
flow: string;
link?: boolean;

View file

@ -40,6 +40,17 @@ mock(new URL("/.well-known/versia", instanceUrl).href, {
headers: {
"Content-Type": "application/json",
},
data: {
versions: ["0.6.0"],
},
},
});
mock(new URL("/.versia/v0.6/instance", instanceUrl).href, {
response: {
headers: {
"Content-Type": "application/vnd.versia+json; charset=utf-8",
},
data: new VersiaEntities.InstanceMetadata({
type: "InstanceMetadata",
name: "Versia",
@ -70,7 +81,7 @@ mock(new URL("/.well-known/versia", instanceUrl).href, {
mock(new URL(`/.versia/v0.6/entities/User/${userId}`, instanceUrl).href, {
response: {
headers: {
"Content-Type": "application/json",
"Content-Type": "application/vnd.versia+json; charset=utf-8",
},
data: new VersiaEntities.User({
id: userId,
@ -132,7 +143,7 @@ describe("Inbox Tests", () => {
const signedRequest = await sign(
instanceKeys.privateKey,
new URL(exampleNote.data.author),
instanceUrl,
new Request(inboxUrl, {
method: "POST",
headers: {
@ -151,6 +162,8 @@ describe("Inbox Tests", () => {
body: signedRequest.body,
});
console.log(await response.text());
expect(response.status).toBe(200);
await sleep(500);
@ -174,7 +187,7 @@ describe("Inbox Tests", () => {
const signedRequest = await sign(
instanceKeys.privateKey,
new URL(exampleRequest.data.author),
instanceUrl,
new Request(inboxUrl, {
method: "POST",
headers: {
@ -227,7 +240,7 @@ describe("Inbox Tests", () => {
const signedRequest = await sign(
instanceKeys.privateKey,
new URL(exampleRequest.data.author),
instanceUrl,
new Request(inboxUrl, {
method: "POST",
headers: {
@ -322,7 +335,7 @@ describe("Inbox Tests", () => {
const signedRequest = await sign(
instanceKeys.privateKey,
new URL(exampleRequest.data.author),
instanceUrl,
new Request(inboxUrl, {
method: "POST",
headers: {
@ -402,7 +415,7 @@ describe("Inbox Tests", () => {
const signedRequest = await sign(
instanceKeys.privateKey,
new URL(exampleRequest.data.author),
instanceUrl,
new Request(inboxUrl, {
method: "POST",
headers: {

View file

@ -4,7 +4,7 @@ import z from "zod";
import { InboxJobType, inboxQueue } from "~/packages/kit/queues/inbox/queue";
export default apiRoute((app) =>
app.get(
app.post(
"/.versia/v0.6/inbox",
describeRoute({
summary: "Instance inbox endpoint",

View file

@ -941,20 +941,19 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
*/
public static async resolve(
reference: VersiaEntities.Reference,
defaultInstance?: Instance,
): Promise<Note | null> {
// Check if note not already in database
if (
!reference.domain ||
!(reference.domain || defaultInstance) ||
reference.domain === config.http.base_url.hostname
) {
return await Note.fromId(reference.id);
}
const instance = await Instance.resolve(reference.domain);
if (!instance) {
return null;
}
const instance = reference.domain
? await Instance.resolve(reference.domain)
: (defaultInstance as Instance);
const foundNote = await Note.fromSql(
and(
@ -963,7 +962,7 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
Notes.authorId,
sql`(
SELECT "Users".id FROM "Users"
WHERE "Users".instanceId = ${instance.id}
WHERE "Users"."instanceId" = ${instance.id}
LIMIT 1
)`,
),
@ -974,7 +973,14 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
return foundNote;
}
return Note.fromVersia(reference);
return Note.fromVersia(
reference.domain
? reference
: new VersiaEntities.Reference(
reference.id,
instance.data.baseUrl,
),
);
}
/**
@ -983,28 +989,45 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
* If the note already exists, it will update it.
* @param versiaNote - Reference or Versia Note representation
*/
public static async fromVersia(
versiaNote: VersiaEntities.Note,
instance: Instance,
): Promise<Note>;
public static async fromVersia(
reference: VersiaEntities.Reference,
): Promise<Note>;
public static async fromVersia(
versiaNote: VersiaEntities.Note | VersiaEntities.Reference,
instance?: Instance,
): Promise<Note> {
if (versiaNote instanceof VersiaEntities.Reference) {
if (!versiaNote.domain) {
throw new Error(
"Cannot fetch Versia note from reference without domain",
);
}
// No bridge support for notes yet
const note = await Instance.federationRequester.fetchEntity(
versiaNote,
VersiaEntities.Note,
);
return Note.fromVersia(note);
const instance = await Instance.resolve(versiaNote.domain);
return Note.fromVersia(note, instance);
}
if (!instance) {
throw new Error("Instance must be provided when fetching note");
}
const { created_at, extensions, group, id, is_sensitive, subject } =
versiaNote.data;
if (!versiaNote.author.domain) {
throw new Error("Entity author domain is missing");
}
const instance = await Instance.resolve(versiaNote.author.domain);
const author = await User.resolve(versiaNote.author);
const author = await User.resolve(versiaNote.author, instance);
if (!author) {
throw new Error("Entity author could not be resolved");
@ -1041,7 +1064,7 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
const mentions = (
await Promise.all(
versiaNote.mentions.map((m) => User.resolve(m)) ?? [],
versiaNote.mentions.map((m) => User.resolve(m, instance)) ?? [],
)
).filter((m) => m !== null);
@ -1052,10 +1075,10 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
: (group as "public" | "followers" | "unlisted");
const reply = versiaNote.repliesTo
? await Note.resolve(versiaNote.repliesTo)
? await Note.resolve(versiaNote.repliesTo, instance)
: null;
const quote = versiaNote.quotes
? await Note.resolve(versiaNote.quotes)
? await Note.resolve(versiaNote.quotes, instance)
: null;
const spoiler = subject ? await sanitizedHtmlStrip(subject) : undefined;

View file

@ -672,9 +672,18 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
* If the user already exists, it will update it.
* @param versiaUser Reference or Versia User representation
*/
public static async fromVersia(
versiaUser: VersiaEntities.User,
instance: Instance,
): Promise<User>;
public static async fromVersia(
versiaUser: VersiaEntities.Reference,
): Promise<User>;
public static async fromVersia(
versiaUser: VersiaEntities.User | VersiaEntities.Reference,
domain: string,
instance?: Instance,
): Promise<User> {
if (versiaUser instanceof VersiaEntities.Reference) {
if (!versiaUser.domain) {
@ -688,7 +697,13 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
VersiaEntities.User,
);
return User.fromVersia(user, versiaUser.domain);
const instance = await Instance.resolve(versiaUser.domain);
return User.fromVersia(user, instance);
}
if (!instance) {
throw new Error("Instance must be provided when fetching user");
}
const {
@ -702,7 +717,6 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
extensions,
} = versiaUser.data;
const instance = await Instance.resolve(domain);
const existingUser = await User.fromSql(
and(eq(Users.instanceId, instance.id), eq(Users.remoteId, id)),
);
@ -788,10 +802,11 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
public static async resolve(
reference: VersiaEntities.Reference,
defaultInstance?: Instance,
): Promise<User> {
// Check if user not already in database
if (
!reference.domain ||
!(reference.domain || defaultInstance) ||
reference.domain === config.http.base_url.hostname
) {
const user = await User.fromId(reference.id);
@ -805,7 +820,9 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
return user;
}
const instance = await Instance.resolve(reference.domain);
const instance = reference.domain
? await Instance.resolve(reference.domain)
: (defaultInstance as Instance);
const foundUser = await User.fromSql(
and(
@ -818,7 +835,14 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
return foundUser;
}
return User.fromVersia(reference, reference.domain);
return User.fromVersia(
reference.domain
? reference
: new VersiaEntities.Reference(
reference.id,
instance.data.baseUrl,
),
);
}
/**

View file

@ -182,32 +182,36 @@ export class InboxProcessor {
shouldCheckSignature && federationInboxLogger.debug`Signature is valid`;
try {
// TODO: Rip out bridge code so this is never null
const instance = this.sender?.instance as Instance;
await new EntitySorter(this.body)
.on(VersiaEntities.Note, (n) => InboxProcessor.processNote(n))
.on(VersiaEntities.Note, (n) =>
InboxProcessor.processNote(n, instance),
)
.on(VersiaEntities.Follow, (f) =>
InboxProcessor.processFollowRequest(f),
InboxProcessor.processFollowRequest(f, instance),
)
.on(VersiaEntities.FollowAccept, (f) =>
InboxProcessor.processFollowAccept(f),
InboxProcessor.processFollowAccept(f, instance),
)
.on(VersiaEntities.FollowReject, (f) =>
InboxProcessor.processFollowReject(f),
InboxProcessor.processFollowReject(f, instance),
)
.on(VersiaEntities.Like, (l) =>
InboxProcessor.processLikeRequest(l),
InboxProcessor.processLikeRequest(l, instance),
)
.on(VersiaEntities.Delete, (d) =>
InboxProcessor.processDelete(d),
InboxProcessor.processDelete(d, instance),
)
.on(VersiaEntities.User, (u) =>
InboxProcessor.processUser(
u,
this.sender?.instance.data.baseUrl ?? "",
),
InboxProcessor.processUser(u, instance),
)
.on(VersiaEntities.Share, (s) =>
InboxProcessor.processShare(s, instance),
)
.on(VersiaEntities.Share, (s) => InboxProcessor.processShare(s))
.on(VersiaEntities.Reaction, (r) =>
InboxProcessor.processReaction(r),
InboxProcessor.processReaction(r, instance),
)
.sort(() => {
throw new ApiError(400, "Unknown entity type");
@ -225,9 +229,10 @@ export class InboxProcessor {
*/
private static async processReaction(
reaction: VersiaEntities.Reaction,
sender: Instance,
): Promise<void> {
const author = await User.resolve(reaction.author);
const note = await Note.resolve(reaction.object);
const author = await User.resolve(reaction.author, sender);
const note = await Note.resolve(reaction.object, sender);
if (!author) {
throw new ApiError(404, "Author not found");
@ -246,7 +251,10 @@ export class InboxProcessor {
* @param {VersiaNote} note - The Note entity to process.
* @returns {Promise<void>}
*/
private static async processNote(note: VersiaEntities.Note): Promise<void> {
private static async processNote(
note: VersiaEntities.Note,
sender: Instance,
): Promise<void> {
// If note has a blocked word
if (
Object.values(note.content?.data ?? {})
@ -262,7 +270,7 @@ export class InboxProcessor {
return;
}
await Note.fromVersia(note);
await Note.fromVersia(note, sender);
}
/**
@ -274,7 +282,7 @@ export class InboxProcessor {
*/
private static async processUser(
user: VersiaEntities.User,
domain: string,
sender: Instance,
): Promise<void> {
if (
config.validation.filters.username.some((filter) =>
@ -303,7 +311,7 @@ export class InboxProcessor {
return;
}
await User.fromVersia(user, domain);
await User.fromVersia(user, sender);
}
/**
@ -314,9 +322,10 @@ export class InboxProcessor {
*/
private static async processFollowRequest(
follow: VersiaEntities.Follow,
sender: Instance,
): Promise<void> {
const author = await User.resolve(follow.author);
const followee = await User.resolve(follow.followee);
const author = await User.resolve(follow.author, sender);
const followee = await User.resolve(follow.followee, sender);
if (!author) {
throw new ApiError(404, "Author not found");
@ -362,9 +371,10 @@ export class InboxProcessor {
*/
private static async processFollowAccept(
followAccept: VersiaEntities.FollowAccept,
sender: Instance,
): Promise<void> {
const author = await User.resolve(followAccept.author);
const follower = await User.resolve(followAccept.follower);
const author = await User.resolve(followAccept.author, sender);
const follower = await User.resolve(followAccept.follower, sender);
if (!author) {
throw new ApiError(404, "Author not found");
@ -397,9 +407,10 @@ export class InboxProcessor {
*/
private static async processFollowReject(
followReject: VersiaEntities.FollowReject,
sender: Instance,
): Promise<void> {
const author = await User.resolve(followReject.author);
const follower = await User.resolve(followReject.follower);
const author = await User.resolve(followReject.author, sender);
const follower = await User.resolve(followReject.follower, sender);
if (!author) {
throw new ApiError(404, "Author not found");
@ -432,9 +443,10 @@ export class InboxProcessor {
*/
private static async processShare(
share: VersiaEntities.Share,
sender: Instance,
): Promise<void> {
const author = await User.resolve(share.author);
const sharedNote = await Note.resolve(share.shared);
const author = await User.resolve(share.author, sender);
const sharedNote = await Note.resolve(share.shared, sender);
if (!author) {
throw new ApiError(404, "Author not found");
@ -455,10 +467,11 @@ export class InboxProcessor {
*/ // JS doesn't allow the use of `delete` as a variable name
public static async processDelete(
delete_: VersiaEntities.Delete,
sender: Instance,
): Promise<void> {
const toDelete = delete_.deleted;
const author = await User.resolve(delete_.author);
const author = await User.resolve(delete_.author, sender);
switch (delete_.data.deleted_type) {
case "Note": {
@ -478,7 +491,7 @@ export class InboxProcessor {
return;
}
case "User": {
const userToDelete = await User.resolve(toDelete);
const userToDelete = await User.resolve(toDelete, sender);
if (!userToDelete) {
throw new ApiError(404, "User to delete not found");
@ -573,9 +586,10 @@ export class InboxProcessor {
*/
private static async processLikeRequest(
like: VersiaEntities.Like,
sender: Instance,
): Promise<void> {
const author = await User.resolve(like.author);
const likedNote = await Note.resolve(like.liked);
const author = await User.resolve(like.author, sender);
const likedNote = await Note.resolve(like.liked, sender);
if (!author) {
throw new ApiError(404, "Author not found");

View file

@ -87,7 +87,9 @@ export const parseMentionsFromText = async (text: string): Promise<User[]> => {
VersiaEntities.User,
);
const user = await User.fromVersia(userEntity, url.hostname);
const instance = await Instance.resolve(url.hostname);
const user = await User.fromVersia(userEntity, instance);
if (user) {
finalList.push(user);

View file

@ -0,0 +1,14 @@
ALTER TABLE "Likes" DROP CONSTRAINT "Likes_uri_unique";--> statement-breakpoint
ALTER TABLE "Notes" DROP CONSTRAINT "Notes_uri_unique";--> statement-breakpoint
ALTER TABLE "Users" DROP CONSTRAINT "Users_uri_unique";--> statement-breakpoint
DROP INDEX "Users_uri_index";--> statement-breakpoint
ALTER TABLE "Likes" ADD COLUMN "remote_id" text;--> statement-breakpoint
ALTER TABLE "Notes" ADD COLUMN "remote_id" text;--> statement-breakpoint
ALTER TABLE "Reaction" ADD COLUMN "remote_id" text;--> statement-breakpoint
ALTER TABLE "Users" ADD COLUMN "remote_id" text;--> statement-breakpoint
ALTER TABLE "Likes" DROP COLUMN "uri";--> statement-breakpoint
ALTER TABLE "Notes" DROP COLUMN "uri";--> statement-breakpoint
ALTER TABLE "Users" DROP COLUMN "uri";--> statement-breakpoint
ALTER TABLE "Users" DROP COLUMN "endpoints";--> statement-breakpoint
ALTER TABLE "Users" DROP COLUMN "public_key";--> statement-breakpoint
ALTER TABLE "Users" DROP COLUMN "private_key";

File diff suppressed because it is too large Load diff

View file

@ -379,6 +379,13 @@
"when": 1765422160004,
"tag": "0053_lively_hellfire_club",
"breakpoints": true
},
{
"idx": 54,
"version": "7",
"when": 1771983340896,
"tag": "0054_good_madelyne_pryor",
"breakpoints": true
}
]
}

View file

@ -0,0 +1,11 @@
diff --git a/dist/index.js b/dist/index.js
index 801380e3811704fc199b80f884ec3ff87d5116f1..381de429cae24d254609c097dad8b52d8865c2cb 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -1,5 +1,5 @@
// @bun
-var $={method:"GET",data:null,headers:new Headers,response:{data:null,headers:new Headers,status:200}};function Q(z){let X=`${z.replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/\\\*/g,"[\\s\\S]*")}$`;return new RegExp(X)}var f=(z)=>(W)=>{let[J,X]=z,[V,Y]=W;if(!(J.toString()===V.toString()||J.match(V)))return!1;let x=X?.method||"GET",H=Y?.method||"GET";if(x.toLowerCase()!==H.toLowerCase())return!1;let G=new Headers(X?.headers),b=new Headers(Y?.headers),K=[...G.keys(),...b.keys()];for(let y of K){let N=G.get(y),g=b.get(y);if(N!==g)return!1}return!0},v=(z,W=$)=>{let{headers:J,data:X,response:V}=W,Y=V?.data??X,w=V?.headers??J,x=Y instanceof Blob||Y instanceof FormData?Y:new Blob([JSON.stringify(Y)]);return new Response(x,{headers:w,status:z})};var Z,P=!1,j=new Map,m=(z,W=$)=>{let J=z instanceof Request?z.url:z,X=J instanceof RegExp?J:new RegExp(Q(J.toString())),V=[...j.entries()].find(f([X.toString(),W]));if(process.env.VERBOSE){if(!V)console.debug("\x1B[1mRegistered mocked request\x1B[0m");else console.debug("\x1B[1mRequest already mocked\x1B[0m \x1B[2mupdated\x1B[0m");console.debug("\x1B[2mURL\x1B[0m",J),console.debug("\x1B[2mPath Pattern\x1B[0m",X),console.debug("\x1B[2mStatus\x1B[0m",W.response?.status||200),console.debug("\x1B[2mMethod\x1B[0m",W.method||"GET"),console.debug(`
+var $={method:"GET",data:null,headers:new Headers,response:{data:null,headers:new Headers,status:200}};function Q(z){let X=`${z.replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/\\\*/g,"[\\s\\S]*")}$`;return new RegExp(X)}var f=(z)=>(W)=>{let[J,X]=z,[V,Y]=W;if(!(J.toString()===V.toString()||J.match(V)))return!1;let x=X?.method||"GET",H=Y?.method||"GET";if(x.toLowerCase()!==H.toLowerCase())return!1;let G=new Headers(X?.headers),b=new Headers(Y?.headers),K=[...G.keys(),...b.keys()];for(let y of K){let N=G.get(y),g=b.get(y);if(g&&N!==g)return!1}return!0},v=(z,W=$)=>{let{headers:J,data:X,response:V}=W,Y=V?.data??X,w=V?.headers??J,x=Y instanceof Blob||Y instanceof FormData?Y:new Blob([JSON.stringify(Y)]);return new Response(x,{headers:w,status:z})};var Z,P=!1,j=new Map,m=(z,W=$)=>{let J=z instanceof Request?z.url:z,X=J instanceof RegExp?J:new RegExp(Q(J.toString())),V=[...j.entries()].find(f([X.toString(),W]));if(process.env.VERBOSE){if(!V)console.debug("\x1B[1mRegistered mocked request\x1B[0m");else console.debug("\x1B[1mRequest already mocked\x1B[0m \x1B[2mupdated\x1B[0m");console.debug("\x1B[2mURL\x1B[0m",J),console.debug("\x1B[2mPath Pattern\x1B[0m",X),console.debug("\x1B[2mStatus\x1B[0m",W.response?.status||200),console.debug("\x1B[2mMethod\x1B[0m",W.method||"GET"),console.debug(`
`)}if(!V)j.set(X,W);else return;if(!Z)Z=globalThis.fetch,globalThis.fetch=F;return!0},h=()=>{if(j.clear(),Z)globalThis.fetch=Z.bind({}),Z=void 0},F=async(z,W)=>{let J=z instanceof Request?z.url:z.toString(),X=z instanceof Request?z:new Request(J),V=[...j.entries()].find(f([J,W]));if(!V){if(P)return Promise.reject(v(404));return await Z(X,W)}if(process.env.VERBOSE)console.debug("\x1B[2mMocked fetch called\x1B[0m",J);if(V[1].throw)throw V[1].throw;let Y=V[1].response?.status||200;return v(Y,V[1])},I=()=>{P=!1},O=()=>{P=!0};export{m as mock,I as enableRealRequests,O as disableRealRequests,h as clearMocks};
//# debugId=6C205C9DF1DC0DBE64756E2164756E21

View file

@ -57,7 +57,12 @@ export const applyToHono = (app: Hono<HonoEnv>): void => {
throw new ApiError(401, "Missing JWT cookie");
}
const result = await verify(jwtCookie, config.authentication.key);
const result = await verify(jwtCookie, config.authentication.key, {
alg: "HS256",
iss: config.http.base_url.toString(),
}).catch(() => {
throw new ApiError(401, "Invalid JWT");
});
const { sub } = result;