mirror of
https://github.com/versia-pub/server.git
synced 2026-03-13 13:59:16 +01:00
guh
This commit is contained in:
parent
aad3ee78d1
commit
8162a5050c
19 changed files with 922 additions and 84 deletions
|
|
@ -1,4 +1,5 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { getConfig } from "@config";
|
||||
import { errorResponse, jsonResponse } from "@response";
|
||||
import {
|
||||
APAccept,
|
||||
|
|
@ -8,6 +9,7 @@ import {
|
|||
APFollow,
|
||||
APObject,
|
||||
APReject,
|
||||
APTombstone,
|
||||
APUpdate,
|
||||
} from "activitypub-types";
|
||||
import { MatchedRoute } from "bun";
|
||||
|
|
@ -26,6 +28,8 @@ export default async (
|
|||
return errorResponse("Method not allowed", 405);
|
||||
}
|
||||
|
||||
const config = getConfig();
|
||||
|
||||
// Process request body
|
||||
const body: APActivity = await req.json();
|
||||
|
||||
|
|
@ -113,7 +117,35 @@ export default async (
|
|||
|
||||
if (!object) return errorResponse("Object not found", 404);
|
||||
|
||||
await object.remove();
|
||||
const activities = await RawActivity.findBy({
|
||||
objects: {
|
||||
id: (body.object as RawObject).id,
|
||||
},
|
||||
});
|
||||
|
||||
if (config.activitypub.use_tombstones) {
|
||||
object.data = {
|
||||
...object.data,
|
||||
type: "Tombstone",
|
||||
deleted: new Date(),
|
||||
formerType: object.data.type,
|
||||
} as APTombstone;
|
||||
|
||||
await object.save();
|
||||
} else {
|
||||
activities.forEach(
|
||||
activity =>
|
||||
(activity.objects = activity.objects.filter(
|
||||
o => o.id !== object.id
|
||||
))
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
activities.map(async activity => await activity.save())
|
||||
);
|
||||
|
||||
await object.remove();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "Accept" as APAccept: {
|
||||
|
|
|
|||
25
server/api/oauth/authorize/index.ts
Normal file
25
server/api/oauth/authorize/index.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { MatchedRoute } from "bun";
|
||||
|
||||
/**
|
||||
* Returns an HTML login form
|
||||
*/
|
||||
export default async (
|
||||
req: Request,
|
||||
matchedRoute: MatchedRoute
|
||||
): Promise<Response> => {
|
||||
const html = Bun.file("./pages/login.html");
|
||||
const css = Bun.file("./pages/uno.css");
|
||||
return new Response(
|
||||
(await html.text())
|
||||
.replace(
|
||||
"{{URL}}",
|
||||
`/auth/login?redirect_uri=${matchedRoute.query.redirect_uri}&response_type=${matchedRoute.query.response_type}&client_id=${matchedRoute.query.client_id}&scopes=${matchedRoute.query.scopes}`
|
||||
)
|
||||
.replace("{{STYLES}}", `<style>${await css.text()}</style>`),
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "text/html",
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { parseRequest } from "@request";
|
||||
import { errorResponse, jsonResponse } from "@response";
|
||||
import { Token } from "~database/entities/Token";
|
||||
|
||||
|
|
@ -5,14 +6,15 @@ import { Token } from "~database/entities/Token";
|
|||
* Allows getting token from OAuth code
|
||||
*/
|
||||
export default async (req: Request): Promise<Response> => {
|
||||
const body = await req.formData();
|
||||
|
||||
const grant_type = body.get("grant_type")?.toString() || null;
|
||||
const code = body.get("code")?.toString() || "";
|
||||
const redirect_uri = body.get("redirect_uri")?.toString() || "";
|
||||
const client_id = body.get("client_id")?.toString() || "";
|
||||
const client_secret = body.get("client_secret")?.toString() || "";
|
||||
const scope = body.get("scope")?.toString() || null;
|
||||
const { grant_type, code, redirect_uri, client_id, client_secret, scope } =
|
||||
await parseRequest<{
|
||||
grant_type: string;
|
||||
code: string;
|
||||
redirect_uri: string;
|
||||
client_id: string;
|
||||
client_secret: string;
|
||||
scope: string;
|
||||
}>(req);
|
||||
|
||||
if (grant_type !== "authorization_code")
|
||||
return errorResponse(
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { getConfig } from "@config";
|
||||
import { parseRequest } from "@request";
|
||||
import { jsonResponse } from "@response";
|
||||
import { tempmailDomains } from "@tempmail";
|
||||
import { User } from "~database/entities/User";
|
||||
|
|
@ -9,14 +10,14 @@ import { User } from "~database/entities/User";
|
|||
export default async (req: Request): Promise<Response> => {
|
||||
// TODO: Add Authorization check
|
||||
|
||||
const body: {
|
||||
const body = await parseRequest<{
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
agreement: boolean;
|
||||
locale: string;
|
||||
reason: string;
|
||||
} = await req.json();
|
||||
}>(req);
|
||||
|
||||
const config = getConfig();
|
||||
|
||||
|
|
@ -63,28 +64,28 @@ export default async (req: Request): Promise<Response> => {
|
|||
config.validation.max_username_size;
|
||||
|
||||
// Check if username is valid
|
||||
if (!body.username.match(/^[a-zA-Z0-9_]+$/))
|
||||
if (!body.username?.match(/^[a-zA-Z0-9_]+$/))
|
||||
errors.details.username.push({
|
||||
error: "ERR_INVALID",
|
||||
description: `must only contain letters, numbers, and underscores`,
|
||||
});
|
||||
|
||||
// Check if username is too long
|
||||
if (body.username.length > config.validation.max_username_size)
|
||||
if ((body.username?.length ?? 0) > config.validation.max_username_size)
|
||||
errors.details.username.push({
|
||||
error: "ERR_TOO_LONG",
|
||||
description: `is too long (maximum is ${config.validation.max_username_size} characters)`,
|
||||
});
|
||||
|
||||
// Check if username is too short
|
||||
if (body.username.length < 3)
|
||||
if ((body.username?.length ?? 0) < 3)
|
||||
errors.details.username.push({
|
||||
error: "ERR_TOO_SHORT",
|
||||
description: `is too short (minimum is 3 characters)`,
|
||||
});
|
||||
|
||||
// Check if username is reserved
|
||||
if (config.validation.username_blacklist.includes(body.username))
|
||||
if (config.validation.username_blacklist.includes(body.username ?? ""))
|
||||
errors.details.username.push({
|
||||
error: "ERR_RESERVED",
|
||||
description: `is reserved`,
|
||||
|
|
@ -99,7 +100,7 @@ export default async (req: Request): Promise<Response> => {
|
|||
|
||||
// Check if email is valid
|
||||
if (
|
||||
!body.email.match(
|
||||
!body.email?.match(
|
||||
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||
)
|
||||
)
|
||||
|
|
@ -110,9 +111,9 @@ export default async (req: Request): Promise<Response> => {
|
|||
|
||||
// Check if email is blocked
|
||||
if (
|
||||
config.validation.email_blacklist.includes(body.email) ||
|
||||
config.validation.email_blacklist.includes(body.email ?? "") ||
|
||||
(config.validation.blacklist_tempmail &&
|
||||
tempmailDomains.domains.includes(body.email.split("@")[1]))
|
||||
tempmailDomains.domains.includes((body.email ?? "").split("@")[1]))
|
||||
)
|
||||
errors.details.email.push({
|
||||
error: "ERR_BLOCKED",
|
||||
|
|
@ -148,9 +149,9 @@ export default async (req: Request): Promise<Response> => {
|
|||
|
||||
const newUser = new User();
|
||||
|
||||
newUser.username = body.username;
|
||||
newUser.email = body.email;
|
||||
newUser.password = await Bun.password.hash(body.password);
|
||||
newUser.username = body.username ?? "";
|
||||
newUser.email = body.email ?? "";
|
||||
newUser.password = await Bun.password.hash(body.password ?? "");
|
||||
|
||||
// TODO: Return access token
|
||||
return new Response();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { getUserByToken } from "@auth";
|
||||
import { getConfig } from "@config";
|
||||
import { parseRequest } from "@request";
|
||||
import { errorResponse, jsonResponse } from "@response";
|
||||
|
||||
/**
|
||||
|
|
@ -21,16 +22,18 @@ export default async (req: Request): Promise<Response> => {
|
|||
if (!user) return errorResponse("Unauthorized", 401);
|
||||
|
||||
const config = getConfig();
|
||||
const body = await req.formData();
|
||||
|
||||
const display_name = body.get("display_name")?.toString() || null;
|
||||
const note = body.get("note")?.toString() || null;
|
||||
// Avatar is a file element
|
||||
const avatar = (body.get("avatar") as File | null) || null;
|
||||
const header = (body.get("header") as File | null) || null;
|
||||
const locked = body.get("locked")?.toString() || null;
|
||||
const bot = body.get("bot")?.toString() || null;
|
||||
const discoverable = body.get("discoverable")?.toString() || null;
|
||||
const { display_name, note, avatar, header, locked, bot, discoverable } =
|
||||
await parseRequest<{
|
||||
display_name: string;
|
||||
note: string;
|
||||
avatar: File;
|
||||
header: File;
|
||||
locked: string;
|
||||
bot: string;
|
||||
discoverable: string;
|
||||
}>(req);
|
||||
|
||||
// TODO: Implement other options like field or source
|
||||
// const source_privacy = body.get("source[privacy]")?.toString() || null;
|
||||
// const source_sensitive = body.get("source[sensitive]")?.toString() || null;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { parseRequest } from "@request";
|
||||
import { errorResponse, jsonResponse } from "@response";
|
||||
import { randomBytes } from "crypto";
|
||||
import { Application } from "~database/entities/Application";
|
||||
|
|
@ -6,12 +7,12 @@ import { Application } from "~database/entities/Application";
|
|||
* Creates a new application to obtain OAuth 2 credentials
|
||||
*/
|
||||
export default async (req: Request): Promise<Response> => {
|
||||
const body = await req.formData();
|
||||
|
||||
const client_name = body.get("client_name")?.toString() || null;
|
||||
const redirect_uris = body.get("redirect_uris")?.toString() || null;
|
||||
const scopes = body.get("scopes")?.toString() || null;
|
||||
const website = body.get("website")?.toString() || null;
|
||||
const { client_name, redirect_uris, scopes, website } = await parseRequest<{
|
||||
client_name: string;
|
||||
redirect_uris: string;
|
||||
scopes: string;
|
||||
website: string;
|
||||
}>(req);
|
||||
|
||||
const application = new Application();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
import { MatchedRoute } from "bun";
|
||||
|
||||
/**
|
||||
* Returns an HTML login form
|
||||
*/
|
||||
export default async (
|
||||
req: Request,
|
||||
matchedRoute: MatchedRoute
|
||||
): Promise<Response> => {
|
||||
const html = Bun.file("./pages/login.html");
|
||||
return new Response(
|
||||
(await html.text()).replace(
|
||||
"{{URL}}",
|
||||
`/auth/login?redirect_uri=${matchedRoute.query.redirect_uri}&response_type=${matchedRoute.query.response_type}&client_id=${matchedRoute.query.client_id}&scopes=${matchedRoute.query.scopes}`
|
||||
),
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "text/html",
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue