server/api/api/v1/apps/index.ts
Jesse Wierzbinski 3d3e64edab
Some checks failed
CodeQL Scan / Analyze (javascript-typescript) (push) Failing after 42s
Build Docker Images / lint (push) Successful in 31s
Build Docker Images / check (push) Successful in 1m3s
Build Docker Images / tests (push) Failing after 6s
Build Docker Images / build (server, Dockerfile, ${{ github.repository_owner }}/server) (push) Has been skipped
Build Docker Images / build (worker, Worker.Dockerfile, ${{ github.repository_owner }}/worker) (push) Has been skipped
Deploy Docs to GitHub Pages / build (push) Failing after 13s
Mirror to Codeberg / Mirror (push) Failing after 0s
Deploy Docs to GitHub Pages / Deploy (push) Has been skipped
Nix Build / check (push) Failing after 33m18s
feat(api): Implement rate limiting
2025-03-27 20:12:00 +01:00

81 lines
2.9 KiB
TypeScript

import { apiRoute, jsonOrForm } from "@/api";
import { randomString } from "@/math";
import { createRoute, z } from "@hono/zod-openapi";
import {
Application as ApplicationSchema,
CredentialApplication as CredentialApplicationSchema,
} from "@versia/client/schemas";
import { Application } from "@versia/kit/db";
import { ApiError } from "~/classes/errors/api-error";
import { rateLimit } from "~/middlewares/rate-limit";
const route = createRoute({
method: "post",
path: "/api/v1/apps",
summary: "Create an application",
description: "Create a new application to obtain OAuth2 credentials.",
externalDocs: {
url: "https://docs.joinmastodon.org/methods/apps/#create",
},
tags: ["Apps"],
middleware: [jsonOrForm(), rateLimit(4)],
request: {
body: {
content: {
"application/json": {
schema: z.object({
client_name: ApplicationSchema.shape.name,
redirect_uris: ApplicationSchema.shape.redirect_uris.or(
ApplicationSchema.shape.redirect_uri.transform(
(u) => u.split("\n"),
),
),
scopes: z
.string()
.default("read")
.transform((s) => s.split(" "))
.openapi({
description: "Space separated list of scopes.",
}),
// Allow empty websites because Traewelling decides to give an empty
// value instead of not providing anything at all
website: ApplicationSchema.shape.website
.optional()
.or(z.literal("").transform(() => undefined)),
}),
},
},
},
},
responses: {
200: {
description:
"Store the client_id and client_secret in your cache, as these will be used to obtain OAuth tokens.",
content: {
"application/json": {
schema: CredentialApplicationSchema,
},
},
},
422: ApiError.validationFailed().schema,
},
});
export default apiRoute((app) =>
app.openapi(route, async (context) => {
const { client_name, redirect_uris, scopes, website } =
context.req.valid("json");
const app = await Application.insert({
name: client_name,
redirectUri: redirect_uris.join("\n"),
scopes: scopes.join(" "),
website,
clientId: randomString(32, "base64url"),
secret: randomString(64, "base64url"),
});
return context.json(app.toApiCredential(), 200);
}),
);