2024-03-10 23:48:14 +01:00
|
|
|
import { apiRoute, applyConfig } from "@api";
|
2024-04-10 04:05:02 +02:00
|
|
|
import { errorResponse, response } from "@response";
|
2024-04-14 02:07:05 +02:00
|
|
|
import { eq } from "drizzle-orm";
|
2024-04-10 04:05:02 +02:00
|
|
|
import type * as Lysand from "lysand-types";
|
2024-04-14 02:07:05 +02:00
|
|
|
import { resolveStatus } from "~database/entities/Status";
|
2024-04-10 07:51:00 +02:00
|
|
|
import {
|
2024-04-14 02:07:05 +02:00
|
|
|
findFirstUser,
|
2024-04-10 07:51:00 +02:00
|
|
|
getRelationshipToOtherUser,
|
|
|
|
|
resolveUser,
|
2024-04-10 10:07:03 +02:00
|
|
|
sendFollowAccept,
|
2024-04-10 07:51:00 +02:00
|
|
|
} from "~database/entities/User";
|
2024-04-14 02:07:05 +02:00
|
|
|
import { db } from "~drizzle/db";
|
|
|
|
|
import { notification, relationship } from "~drizzle/schema";
|
2023-11-04 04:34:31 +01:00
|
|
|
|
|
|
|
|
export const meta = applyConfig({
|
2024-04-07 07:30:49 +02:00
|
|
|
allowedMethods: ["POST"],
|
|
|
|
|
auth: {
|
|
|
|
|
required: false,
|
|
|
|
|
},
|
|
|
|
|
ratelimits: {
|
|
|
|
|
duration: 60,
|
|
|
|
|
max: 500,
|
|
|
|
|
},
|
2024-04-10 04:05:02 +02:00
|
|
|
route: "/users/:uuid",
|
2023-11-04 04:34:31 +01:00
|
|
|
});
|
|
|
|
|
|
2024-03-10 23:48:14 +01:00
|
|
|
export default apiRoute(async (req, matchedRoute, extraData) => {
|
2024-04-10 04:05:02 +02:00
|
|
|
const uuid = matchedRoute.params.uuid;
|
2024-04-07 07:30:49 +02:00
|
|
|
|
2024-04-14 02:07:05 +02:00
|
|
|
const user = await findFirstUser({
|
|
|
|
|
where: (user, { eq }) => eq(user.id, uuid),
|
2024-04-07 07:30:49 +02:00
|
|
|
});
|
|
|
|
|
|
2024-04-10 04:05:02 +02:00
|
|
|
if (!user) {
|
|
|
|
|
return errorResponse("User not found", 404);
|
2024-04-07 07:30:49 +02:00
|
|
|
}
|
|
|
|
|
|
2024-04-10 04:05:02 +02:00
|
|
|
// Process incoming request
|
|
|
|
|
const body = extraData.parsedRequest as Lysand.Entity;
|
2024-04-07 07:30:49 +02:00
|
|
|
|
2024-04-10 04:05:02 +02:00
|
|
|
// Verify request signature
|
|
|
|
|
// TODO: Check if instance is defederated
|
|
|
|
|
// biome-ignore lint/correctness/noConstantCondition: Temporary
|
|
|
|
|
if (true) {
|
|
|
|
|
// request is a Request object containing the previous request
|
|
|
|
|
|
|
|
|
|
const signatureHeader = req.headers.get("Signature");
|
|
|
|
|
const origin = req.headers.get("Origin");
|
2024-04-07 07:30:49 +02:00
|
|
|
const date = req.headers.get("Date");
|
|
|
|
|
|
2024-04-10 04:05:02 +02:00
|
|
|
if (!signatureHeader) {
|
|
|
|
|
return errorResponse("Missing Signature header", 400);
|
2024-04-07 07:30:49 +02:00
|
|
|
}
|
|
|
|
|
|
2024-04-10 04:05:02 +02:00
|
|
|
if (!origin) {
|
|
|
|
|
return errorResponse("Missing Origin header", 400);
|
2024-04-07 07:30:49 +02:00
|
|
|
}
|
|
|
|
|
|
2024-04-10 04:05:02 +02:00
|
|
|
if (!date) {
|
|
|
|
|
return errorResponse("Missing Date header", 400);
|
2024-04-07 07:30:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const signature = signatureHeader
|
|
|
|
|
.split("signature=")[1]
|
|
|
|
|
.replace(/"/g, "");
|
|
|
|
|
|
|
|
|
|
const digest = await crypto.subtle.digest(
|
|
|
|
|
"SHA-256",
|
2024-04-10 04:05:02 +02:00
|
|
|
new TextEncoder().encode(JSON.stringify(body)),
|
2024-04-07 07:30:49 +02:00
|
|
|
);
|
|
|
|
|
|
2024-04-10 04:05:02 +02:00
|
|
|
const keyId = signatureHeader
|
|
|
|
|
.split("keyId=")[1]
|
|
|
|
|
.split(",")[0]
|
|
|
|
|
.replace(/"/g, "");
|
|
|
|
|
|
2024-04-10 09:04:46 +02:00
|
|
|
console.log(`Resolving keyId ${keyId}`);
|
|
|
|
|
|
2024-04-10 07:59:36 +02:00
|
|
|
const sender = await resolveUser(keyId);
|
2024-04-07 07:30:49 +02:00
|
|
|
|
2024-04-10 04:05:02 +02:00
|
|
|
if (!sender) {
|
|
|
|
|
return errorResponse("Invalid keyId", 400);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const public_key = await crypto.subtle.importKey(
|
2024-04-07 07:30:49 +02:00
|
|
|
"spki",
|
2024-04-10 04:05:02 +02:00
|
|
|
Uint8Array.from(atob(sender.publicKey), (c) => c.charCodeAt(0)),
|
2024-04-07 07:30:49 +02:00
|
|
|
"Ed25519",
|
|
|
|
|
false,
|
|
|
|
|
["verify"],
|
|
|
|
|
);
|
|
|
|
|
|
2024-04-10 04:05:02 +02:00
|
|
|
const expectedSignedString =
|
|
|
|
|
`(request-target): ${req.method.toLowerCase()} ${
|
|
|
|
|
new URL(req.url).pathname
|
|
|
|
|
}\n` +
|
|
|
|
|
`host: ${new URL(req.url).host}\n` +
|
|
|
|
|
`date: ${date}\n` +
|
|
|
|
|
`digest: SHA-256=${btoa(
|
|
|
|
|
String.fromCharCode(...new Uint8Array(digest)),
|
|
|
|
|
)}\n`;
|
|
|
|
|
|
2024-04-07 07:30:49 +02:00
|
|
|
// Check if signed string is valid
|
|
|
|
|
const isValid = await crypto.subtle.verify(
|
|
|
|
|
"Ed25519",
|
2024-04-10 04:05:02 +02:00
|
|
|
public_key,
|
|
|
|
|
Uint8Array.from(atob(signature), (c) => c.charCodeAt(0)),
|
2024-04-07 07:30:49 +02:00
|
|
|
new TextEncoder().encode(expectedSignedString),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!isValid) {
|
2024-04-10 04:05:02 +02:00
|
|
|
return errorResponse("Invalid signature", 400);
|
2024-04-07 07:30:49 +02:00
|
|
|
}
|
2024-04-10 04:05:02 +02:00
|
|
|
}
|
2024-04-07 07:30:49 +02:00
|
|
|
|
2024-04-10 04:05:02 +02:00
|
|
|
// Add sent data to database
|
|
|
|
|
switch (body.type) {
|
2024-04-07 07:30:49 +02:00
|
|
|
case "Note": {
|
2024-04-10 04:05:02 +02:00
|
|
|
const note = body as Lysand.Note;
|
2024-04-07 07:30:49 +02:00
|
|
|
|
2024-04-10 07:51:00 +02:00
|
|
|
const account = await resolveUser(note.author);
|
2024-04-07 07:30:49 +02:00
|
|
|
|
2024-04-10 04:05:02 +02:00
|
|
|
if (!account) {
|
|
|
|
|
return errorResponse("Author not found", 400);
|
2024-04-07 07:30:49 +02:00
|
|
|
}
|
|
|
|
|
|
2024-04-10 10:37:58 +02:00
|
|
|
const newStatus = await resolveStatus(undefined, note).catch(
|
|
|
|
|
(e) => {
|
|
|
|
|
console.error(e);
|
|
|
|
|
return null;
|
2024-04-07 07:30:49 +02:00
|
|
|
},
|
2024-04-10 04:05:02 +02:00
|
|
|
);
|
|
|
|
|
|
2024-04-10 10:37:58 +02:00
|
|
|
if (!newStatus) {
|
|
|
|
|
return errorResponse("Failed to add status", 500);
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-10 04:05:02 +02:00
|
|
|
return response("Note created", 201);
|
2024-04-07 07:30:49 +02:00
|
|
|
}
|
2024-04-10 07:51:00 +02:00
|
|
|
case "Follow": {
|
|
|
|
|
const follow = body as Lysand.Follow;
|
|
|
|
|
|
|
|
|
|
const account = await resolveUser(follow.author);
|
|
|
|
|
|
|
|
|
|
if (!account) {
|
|
|
|
|
return errorResponse("Author not found", 400);
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-14 02:07:05 +02:00
|
|
|
const foundRelationship = await getRelationshipToOtherUser(
|
2024-04-10 07:51:00 +02:00
|
|
|
account,
|
|
|
|
|
user,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Check if already following
|
2024-04-14 02:07:05 +02:00
|
|
|
if (foundRelationship.following) {
|
2024-04-10 07:51:00 +02:00
|
|
|
return response("Already following", 200);
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-14 02:07:05 +02:00
|
|
|
await db
|
|
|
|
|
.update(relationship)
|
|
|
|
|
.set({
|
2024-04-10 07:51:00 +02:00
|
|
|
following: !user.isLocked,
|
|
|
|
|
requested: user.isLocked,
|
|
|
|
|
showingReblogs: true,
|
|
|
|
|
notifying: true,
|
|
|
|
|
languages: [],
|
2024-04-14 02:07:05 +02:00
|
|
|
})
|
|
|
|
|
.where(eq(relationship.id, foundRelationship.id));
|
2024-04-10 07:51:00 +02:00
|
|
|
|
2024-04-14 02:07:05 +02:00
|
|
|
await db.insert(notification).values({
|
|
|
|
|
accountId: account.id,
|
|
|
|
|
type: user.isLocked ? "follow_request" : "follow",
|
|
|
|
|
notifiedId: user.id,
|
2024-04-10 07:51:00 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!user.isLocked) {
|
|
|
|
|
// Federate FollowAccept
|
2024-04-10 10:07:03 +02:00
|
|
|
await sendFollowAccept(account, user);
|
2024-04-10 07:51:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response("Follow request sent", 200);
|
|
|
|
|
}
|
2024-04-10 09:24:23 +02:00
|
|
|
case "FollowAccept": {
|
|
|
|
|
const followAccept = body as Lysand.FollowAccept;
|
|
|
|
|
|
2024-04-10 09:45:20 +02:00
|
|
|
console.log(followAccept);
|
|
|
|
|
|
2024-04-10 09:24:23 +02:00
|
|
|
const account = await resolveUser(followAccept.author);
|
|
|
|
|
|
|
|
|
|
if (!account) {
|
|
|
|
|
return errorResponse("Author not found", 400);
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-10 09:45:20 +02:00
|
|
|
console.log(account);
|
|
|
|
|
|
2024-04-14 02:07:05 +02:00
|
|
|
const foundRelationship = await getRelationshipToOtherUser(
|
2024-04-10 09:24:23 +02:00
|
|
|
user,
|
|
|
|
|
account,
|
|
|
|
|
);
|
|
|
|
|
|
2024-04-14 02:07:05 +02:00
|
|
|
console.log(foundRelationship);
|
2024-04-10 09:45:20 +02:00
|
|
|
|
2024-04-14 02:07:05 +02:00
|
|
|
if (!foundRelationship.requested) {
|
2024-04-10 09:24:23 +02:00
|
|
|
return response("There is no follow request to accept", 200);
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-14 02:07:05 +02:00
|
|
|
await db
|
|
|
|
|
.update(relationship)
|
|
|
|
|
.set({
|
2024-04-10 09:24:23 +02:00
|
|
|
following: true,
|
|
|
|
|
requested: false,
|
2024-04-14 02:07:05 +02:00
|
|
|
})
|
|
|
|
|
.where(eq(relationship.id, foundRelationship.id));
|
2024-04-10 09:24:23 +02:00
|
|
|
|
|
|
|
|
return response("Follow request accepted", 200);
|
|
|
|
|
}
|
2024-04-10 10:07:03 +02:00
|
|
|
case "FollowReject": {
|
|
|
|
|
const followReject = body as Lysand.FollowReject;
|
|
|
|
|
|
|
|
|
|
const account = await resolveUser(followReject.author);
|
|
|
|
|
|
|
|
|
|
if (!account) {
|
|
|
|
|
return errorResponse("Author not found", 400);
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-14 02:07:05 +02:00
|
|
|
const foundRelationship = await getRelationshipToOtherUser(
|
2024-04-10 10:07:03 +02:00
|
|
|
user,
|
|
|
|
|
account,
|
|
|
|
|
);
|
|
|
|
|
|
2024-04-14 02:07:05 +02:00
|
|
|
if (!foundRelationship.requested) {
|
2024-04-10 10:07:03 +02:00
|
|
|
return response("There is no follow request to reject", 200);
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-14 02:07:05 +02:00
|
|
|
await db
|
|
|
|
|
.update(relationship)
|
|
|
|
|
.set({
|
2024-04-10 10:07:03 +02:00
|
|
|
requested: false,
|
|
|
|
|
following: false,
|
2024-04-14 02:07:05 +02:00
|
|
|
})
|
|
|
|
|
.where(eq(relationship.id, foundRelationship.id));
|
2024-04-10 10:07:03 +02:00
|
|
|
|
|
|
|
|
return response("Follow request rejected", 200);
|
|
|
|
|
}
|
2024-04-07 07:30:49 +02:00
|
|
|
default: {
|
2024-04-10 04:05:02 +02:00
|
|
|
return errorResponse("Unknown object type", 400);
|
2024-04-07 07:30:49 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-10 04:05:02 +02:00
|
|
|
//return jsonResponse(userToLysand(user));
|
2024-03-10 23:48:14 +01:00
|
|
|
});
|