From 8162a5050c19fd2b31a2e630d499966928d49da1 Mon Sep 17 00:00:00 2001 From: Jesse Wierzbinski Date: Thu, 14 Sep 2023 15:22:27 -1000 Subject: [PATCH] guh --- README.md | 13 - bun.lockb | Bin 199067 -> 228319 bytes index.ts | 2 - package.json | 5 +- pages/login.html | 446 +++++++++++++++++- pages/uno.css | 155 ++++++ server/api/[username]/inbox/index.ts | 34 +- server/api/oauth/authorize/index.ts | 25 + server/api/{v1 => }/oauth/token/index.ts | 18 +- server/api/v1/accounts/index.ts | 25 +- .../v1/accounts/update_credentials/index.ts | 21 +- server/api/v1/apps/index.ts | 13 +- server/api/v1/oauth/authorize/index.ts | 22 - tests/inbox.test.ts | 56 +++ tests/oauth.test.ts | 4 +- tests/test-utils.ts | 0 uno.config.ts | 22 + utils/config.ts | 91 +++- utils/request.ts | 54 +++ 19 files changed, 922 insertions(+), 84 deletions(-) create mode 100644 pages/uno.css create mode 100644 server/api/oauth/authorize/index.ts rename server/api/{v1 => }/oauth/token/index.ts (66%) delete mode 100644 server/api/v1/oauth/authorize/index.ts create mode 100644 tests/test-utils.ts create mode 100644 uno.config.ts create mode 100644 utils/request.ts diff --git a/README.md b/README.md index e8ecc79b..4925c6f3 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,2 @@ # fedi-project -To install dependencies: - -```bash -bun install -``` - -To run: - -```bash -bun run index.ts -``` - -This project was created using `bun init` in bun v0.8.1. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/bun.lockb b/bun.lockb index b2a94eb17941b456977dd88715e6e0f4ed767006..e2ea5ecd92e4898d14aa6e3633dc551a25f8d238 100755 GIT binary patch delta 64000 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 HjRXG=y86D+ diff --git a/index.ts b/index.ts index 0d3cf121..058a7193 100644 --- a/index.ts +++ b/index.ts @@ -19,8 +19,6 @@ Bun.serve({ async fetch(req) { const matchedRoute = router.match(req); - console.log(req.url); - if (matchedRoute) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call return (await import(matchedRoute.filePath)).default( diff --git a/package.json b/package.json index a5702f7c..8ff8b58d 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,11 @@ "module": "index.ts", "type": "module", "devDependencies": { + "@julr/unocss-preset-forms": "^0.0.5", "@types/jsonld": "^1.5.9", "@typescript-eslint/eslint-plugin": "^6.6.0", "@typescript-eslint/parser": "^6.6.0", + "@unocss/cli": "^0.55.7", "activitypub-types": "^1.0.3", "bun-types": "latest", "eslint": "^8.49.0", @@ -14,7 +16,8 @@ "eslint-formatter-summary": "^1.1.0", "eslint-plugin-prettier": "^5.0.0", "prettier": "^3.0.3", - "typescript": "^5.2.2" + "typescript": "^5.2.2", + "unocss": "^0.55.7" }, "peerDependencies": { "typescript": "^5.0.0" diff --git a/pages/login.html b/pages/login.html index 0a90d7c1..03ac18e4 100644 --- a/pages/login.html +++ b/pages/login.html @@ -1,13 +1,445 @@ + -Login with FediProject - + Login with FediProject + {{STYLES}} + + -
- - - -
+
+
+
+
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+
+
\ No newline at end of file diff --git a/pages/uno.css b/pages/uno.css new file mode 100644 index 00000000..4e3b1a12 --- /dev/null +++ b/pages/uno.css @@ -0,0 +1,155 @@ +/* layer: preflights */ +*,::before,::after{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgba(0,0,0,0);--un-ring-shadow:0 0 rgba(0,0,0,0);--un-shadow-inset: ;--un-shadow:0 0 rgba(0,0,0,0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgba(147,197,253,0.5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: ;}::backdrop{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgba(0,0,0,0);--un-ring-shadow:0 0 rgba(0,0,0,0);--un-shadow-inset: ;--un-shadow:0 0 rgba(0,0,0,0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgba(147,197,253,0.5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: ;} +[type='text'], [type='email'], [type='url'], [type='password'], [type='number'], [type='date'], [type='datetime-local'], [type='month'], [type='search'], [type='tel'], [type='time'], [type='week'], [multiple], textarea, select { appearance: none; +background-color: #fff; +border-color: #6b7280; +border-width: 1px; +border-radius: 0; +padding-top: 0.5rem; +padding-right: 0.75rem; +padding-bottom: 0.5rem; +padding-left: 0.75rem; +font-size: 1rem; +line-height: 1.5rem; +--un-shadow: 0 0 #0000; } +[type='text']:focus, [type='email']:focus, [type='url']:focus, [type='password']:focus, [type='number']:focus, [type='date']:focus, [type='datetime-local']:focus, [type='month']:focus, [type='search']:focus, [type='tel']:focus, [type='time']:focus, [type='week']:focus, [multiple]:focus, textarea:focus, select:focus { outline: 2px solid transparent; +outline-offset: 2px; +--un-ring-inset: var(--un-empty,/*!*/ /*!*/); +--un-ring-offset-width: 0px; +--un-ring-offset-color: #fff; +--un-ring-color: #2563eb; +--un-ring-offset-shadow: var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color); +--un-ring-shadow: var(--un-ring-inset) 0 0 0 calc(1px + var(--un-ring-offset-width)) var(--un-ring-color); +box-shadow: var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow); +border-color: #2563eb; } +input::placeholder, textarea::placeholder { color: #6b7280; +opacity: 1; } +::-webkit-datetime-edit-fields-wrapper { padding: 0; } +::-webkit-date-and-time-value { min-height: 1.5em; } +::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field { padding-top: 0; +padding-bottom: 0; } +select { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); +background-position: right 0.5rem center; +background-repeat: no-repeat; +background-size: 1.5em 1.5em; +padding-right: 2.5rem; +print-color-adjust: exact; } +[multiple] { background-image: initial; +background-position: initial; +background-repeat: unset; +background-size: initial; +padding-right: 0.75rem; +print-color-adjust: unset; } +[type='checkbox'], [type='radio'] { appearance: none; +padding: 0; +print-color-adjust: exact; +display: inline-block; +vertical-align: middle; +background-origin: border-box; +user-select: none; +flex-shrink: 0; +height: 1rem; +width: 1rem; +color: #2563eb; +background-color: #fff; +border-color: #6b7280; +border-width: 1px; +--un-shadow: 0 0 #0000; } +[type='checkbox'] { border-radius: 0; } +[type='radio'] { border-radius: 100%; } +[type='checkbox']:focus, [type='radio']:focus { outline: 2px solid transparent; +outline-offset: 2px; +--un-ring-inset: var(--un-empty,/*!*/ /*!*/); +--un-ring-offset-width: 2px; +--un-ring-offset-color: #fff; +--un-ring-color: #2563eb; +--un-ring-offset-shadow: var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color); +--un-ring-shadow: var(--un-ring-inset) 0 0 0 calc(2px + var(--un-ring-offset-width)) var(--un-ring-color); +box-shadow: var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow); } +[type='checkbox']:checked, [type='radio']:checked { border-color: transparent; +background-color: currentColor; +background-size: 100% 100%; +background-position: center; +background-repeat: no-repeat; } +[type='checkbox']:checked { background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e"); } +[type='radio']:checked { background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e"); } +[type='checkbox']:checked:hover, [type='checkbox']:checked:focus, [type='radio']:checked:hover, [type='radio']:checked:focus { border-color: transparent; +background-color: currentColor; } +[type='checkbox']:indeterminate { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e"); +border-color: transparent; +background-color: currentColor; +background-size: 100% 100%; +background-position: center; +background-repeat: no-repeat; } +[type='checkbox']:indeterminate:hover, [type='checkbox']:indeterminate:focus { border-color: transparent; +background-color: currentColor; } +[type='file'] { background: unset; +border-color: inherit; +border-width: 0; +border-radius: 0; +padding: 0; +font-size: unset; +line-height: inherit; } +[type='file']:focus { outline: 1px solid ButtonText , 1px auto -webkit-focus-ring-color; } +/* layer: default */ +.visible{visibility:visible;} +.relative{position:relative;} +.mt-10{margin-top:2.5rem;} +.mt-2{margin-top:0.5rem;} +.block{display:block;} +.contents{display:contents;} +.list-item{display:list-item;} +.hidden{display:none;} +.h6{height:1.5rem;} +.min-h-screen{min-height:100vh;} +.w-full{width:100%;} +.flex{display:flex;} +.flex-col{flex-direction:column;} +.table{display:table;} +.border-collapse{border-collapse:collapse;} +.transform{transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z));} +.resize{resize:both;} +.items-center{align-items:center;} +.justify-center{justify-content:center;} +.justify-between{justify-content:space-between;} +.space-y-6>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(1.5rem * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(1.5rem * var(--un-space-y-reverse));} +.border{border-width:1px;} +.border-0{border-width:0;} +.rounded-md{border-radius:0.375rem;} +.\!bg-indigo-600{--un-bg-opacity:1 !important;background-color:rgba(79,70,229,var(--un-bg-opacity)) !important;} +.hover\:\!bg-indigo-500:hover{--un-bg-opacity:1 !important;background-color:rgba(99,102,241,var(--un-bg-opacity)) !important;} +.px-3{padding-left:0.75rem;padding-right:0.75rem;} +.px-6{padding-left:1.5rem;padding-right:1.5rem;} +.py-1\.5{padding-top:0.375rem;padding-bottom:0.375rem;} +.py-12{padding-top:3rem;padding-bottom:3rem;} +.text-sm{font-size:0.875rem;line-height:1.25rem;} +.font-medium{font-weight:500;} +.font-semibold{font-weight:600;} +.leading-6{line-height:1.5rem;} +.text-gray-900{--un-text-opacity:1;color:rgba(17,24,39,var(--un-text-opacity));} +.text-white{--un-text-opacity:1;color:rgba(255,255,255,var(--un-text-opacity));} +.placeholder\:text-gray-400::placeholder{--un-text-opacity:1;color:rgba(156,163,175,var(--un-text-opacity));} +.underline{text-decoration-line:underline;} +.tab{-moz-tab-size:4;-o-tab-size:4;tab-size:4;} +.shadow-sm{--un-shadow:var(--un-shadow-inset) 0 1px 2px 0 var(--un-shadow-color, rgba(0,0,0,0.05));box-shadow:var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow);} +.focus-visible\:outline-2:focus-visible{outline-width:2px;} +.focus-visible\:outline-indigo-600:focus-visible{--un-outline-color-opacity:1;outline-color:rgba(79,70,229,var(--un-outline-color-opacity));} +.focus-visible\:outline-offset-2:focus-visible{outline-offset:2px;} +.outline{outline-style:solid;} +.focus-visible\:outline:focus-visible{outline-style:solid;} +.ring-1{--un-ring-width:1px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width) + var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow);} +.focus\:ring-2:focus{--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width) + var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow);} +.ring-gray-300{--un-ring-opacity:1;--un-ring-color:rgba(209,213,219,var(--un-ring-opacity));} +.focus\:ring-indigo-600:focus{--un-ring-opacity:1;--un-ring-color:rgba(79,70,229,var(--un-ring-opacity));} +.ring-inset{--un-ring-inset:inset;} +.focus\:ring-inset:focus{--un-ring-inset:inset;} +@media (min-width: 640px){ +.sm\:mx-auto{margin-left:auto;margin-right:auto;} +.sm\:max-w-sm{max-width:24rem;} +.sm\:w-full{width:100%;} +.sm\:text-sm{font-size:0.875rem;line-height:1.25rem;} +.sm\:leading-6{line-height:1.5rem;} +} +@media (min-width: 1024px){ +.lg\:px-8{padding-left:2rem;padding-right:2rem;} +} \ No newline at end of file diff --git a/server/api/[username]/inbox/index.ts b/server/api/[username]/inbox/index.ts index ff373a95..c714be9b 100644 --- a/server/api/[username]/inbox/index.ts +++ b/server/api/[username]/inbox/index.ts @@ -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: { diff --git a/server/api/oauth/authorize/index.ts b/server/api/oauth/authorize/index.ts new file mode 100644 index 00000000..3f395c41 --- /dev/null +++ b/server/api/oauth/authorize/index.ts @@ -0,0 +1,25 @@ +import { MatchedRoute } from "bun"; + +/** + * Returns an HTML login form + */ +export default async ( + req: Request, + matchedRoute: MatchedRoute +): Promise => { + 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}}", ``), + { + headers: { + "Content-Type": "text/html", + }, + } + ); +}; diff --git a/server/api/v1/oauth/token/index.ts b/server/api/oauth/token/index.ts similarity index 66% rename from server/api/v1/oauth/token/index.ts rename to server/api/oauth/token/index.ts index 6d00f772..dc1284cf 100644 --- a/server/api/v1/oauth/token/index.ts +++ b/server/api/oauth/token/index.ts @@ -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 => { - 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( diff --git a/server/api/v1/accounts/index.ts b/server/api/v1/accounts/index.ts index c9f74a00..b7622ae2 100644 --- a/server/api/v1/accounts/index.ts +++ b/server/api/v1/accounts/index.ts @@ -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 => { // 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 => { 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 => { // 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 => { // 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 => { 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(); diff --git a/server/api/v1/accounts/update_credentials/index.ts b/server/api/v1/accounts/update_credentials/index.ts index a303df9d..b10c5a8d 100644 --- a/server/api/v1/accounts/update_credentials/index.ts +++ b/server/api/v1/accounts/update_credentials/index.ts @@ -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 => { 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; diff --git a/server/api/v1/apps/index.ts b/server/api/v1/apps/index.ts index 32e282a6..8191b7f4 100644 --- a/server/api/v1/apps/index.ts +++ b/server/api/v1/apps/index.ts @@ -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 => { - 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(); diff --git a/server/api/v1/oauth/authorize/index.ts b/server/api/v1/oauth/authorize/index.ts deleted file mode 100644 index c2b72f31..00000000 --- a/server/api/v1/oauth/authorize/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { MatchedRoute } from "bun"; - -/** - * Returns an HTML login form - */ -export default async ( - req: Request, - matchedRoute: MatchedRoute -): Promise => { - 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", - }, - } - ); -}; diff --git a/tests/inbox.test.ts b/tests/inbox.test.ts index 882dad6b..b211574e 100644 --- a/tests/inbox.test.ts +++ b/tests/inbox.test.ts @@ -150,6 +150,62 @@ describe("POST /@test/inbox", () => { published: "2021-01-01T00:00:00.000Z", }); }); + + test("should delete the Note object", async () => { + const response = await fetch( + `${config.http.base_url}:${config.http.port}/@test/inbox/`, + { + method: "POST", + headers: { + "Content-Type": "application/activity+json", + }, + body: JSON.stringify({ + "@context": "https://www.w3.org/ns/activitystreams", + type: "Delete", + id: "https://example.com/notes/1/activity", + actor: `${config.http.base_url}:${config.http.port}/@test`, + to: ["https://www.w3.org/ns/activitystreams#Public"], + cc: [], + published: "2021-01-03T00:00:00.000Z", + object: { + "@context": "https://www.w3.org/ns/activitystreams", + id: "https://example.com/notes/1", + type: "Note", + content: "This note has been deleted!", + summary: null, + inReplyTo: null, + published: "2021-01-01T00:00:00.000Z", + }, + }), + } + ); + + expect(response.status).toBe(200); + expect(response.headers.get("content-type")).toBe("application/json"); + + const activity = await RawActivity.createQueryBuilder("activity") + // Where id is part of the jsonb column 'data' + .where("activity.data->>'id' = :id", { + id: "https://example.com/notes/1/activity", + }) + .leftJoinAndSelect("activity.objects", "objects") + // Sort by most recent + .orderBy("activity.data->>'published'", "DESC") + .getOne(); + + expect(activity).not.toBeUndefined(); + expect(activity?.data).toEqual({ + "@context": "https://www.w3.org/ns/activitystreams", + type: "Delete", + id: "https://example.com/notes/1/activity", + actor: `${config.http.base_url}:${config.http.port}/@test`, + to: ["https://www.w3.org/ns/activitystreams#Public"], + cc: [], + published: "2021-01-03T00:00:00.000Z", + }); + + expect(activity?.objects).toHaveLength(0); + }); }); afterAll(async () => { diff --git a/tests/oauth.test.ts b/tests/oauth.test.ts index baf4e202..971fc2c6 100644 --- a/tests/oauth.test.ts +++ b/tests/oauth.test.ts @@ -89,7 +89,7 @@ describe("POST /auth/login/", () => { }); }); -describe("POST /v1/oauth/token/", () => { +describe("POST /oauth/token/", () => { test("should get an access token", async () => { const formData = new FormData(); @@ -101,7 +101,7 @@ describe("POST /v1/oauth/token/", () => { formData.append("scope", "read write"); const response = await fetch( - `${config.http.base_url}:${config.http.port}/v1/oauth/token/`, + `${config.http.base_url}:${config.http.port}/oauth/token/`, { method: "POST", body: formData, diff --git a/tests/test-utils.ts b/tests/test-utils.ts new file mode 100644 index 00000000..e69de29b diff --git a/uno.config.ts b/uno.config.ts new file mode 100644 index 00000000..ff472c3a --- /dev/null +++ b/uno.config.ts @@ -0,0 +1,22 @@ +import { + defineConfig, + presetUno, + presetTypography, + presetWebFonts, +} from "unocss"; +import { presetForms } from "@julr/unocss-preset-forms"; + +export default defineConfig({ + presets: [ + presetUno(), + presetTypography({ + cssExtend: { + "h1,h2,h3,h4,h5.h6": { + "font-family": "'Poppins'", + }, + }, + }), + presetWebFonts(), + presetForms(), + ], +}); diff --git a/utils/config.ts b/utils/config.ts index 903385fd..393291b6 100644 --- a/utils/config.ts +++ b/utils/config.ts @@ -8,10 +8,12 @@ export interface ConfigType { password: string; database: string; }; + http: { port: number; base_url: string; }; + validation: { max_displayname_size: number; max_bio_size: number; @@ -28,11 +30,98 @@ export interface ConfigType { email_blacklist: string[]; url_scheme_whitelist: string[]; }; + + activitypub: { + use_tombstones: boolean; + }; [key: string]: unknown; } +export const configDefaults: ConfigType = { + http: { + port: 3000, + base_url: "http://0.0.0.0", + }, + database: { + host: "localhost", + port: 5432, + username: "postgres", + password: "postgres", + database: "fediproject", + }, + validation: { + max_displayname_size: 50, + max_bio_size: 6000, + max_note_size: 5000, + max_avatar_size: 5_000_000, + max_header_size: 5_000_000, + max_media_size: 40_000_000, + max_media_attachments: 4, + max_media_description_size: 1000, + max_username_size: 30, + + username_blacklist: [ + ".well-known", + "~", + "about", + "activities", + "api", + "auth", + "dev", + "inbox", + "internal", + "main", + "media", + "nodeinfo", + "notice", + "oauth", + "objects", + "proxy", + "push", + "registration", + "relay", + "settings", + "status", + "tag", + "users", + "web", + "search", + "mfa", + ], + + blacklist_tempmail: false, + + email_blacklist: [], + + url_scheme_whitelist: [ + "http", + "https", + "ftp", + "dat", + "dweb", + "gopher", + "hyper", + "ipfs", + "ipns", + "irc", + "xmpp", + "ircs", + "magnet", + "mailto", + "mumble", + "ssb", + ], + }, + activitypub: { + use_tombstones: true, + }, +}; + export const getConfig = () => { - return data as ConfigType; + return { + ...configDefaults, + ...(data as ConfigType), + }; }; export const getHost = () => { diff --git a/utils/request.ts b/utils/request.ts new file mode 100644 index 00000000..39692a84 --- /dev/null +++ b/utils/request.ts @@ -0,0 +1,54 @@ +/** + * Takes a request, and turns FormData or query parameters + * into a JSON object as would be returned by req.json() + * This is a translation layer that allows clients to use + * either FormData, query parameters, or JSON in the request + * @param request The request to parse + */ +export async function parseRequest(request: Request): Promise> { + const formData = await request.formData(); + const query = new URL(request.url).searchParams; + + // if request contains a JSON body + if (request.headers.get("Content-Type")?.includes("application/json")) { + return (await request.json()) as T; + } + + // If request contains FormData + if (request.headers.get("Content-Type")?.includes("multipart/form-data")) { + if ([...formData.entries()].length > 0) { + const data: Record = {}; + + for (const [key, value] of formData.entries()) { + // If object, parse as JSON + try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-base-to-string + data[key] = JSON.parse(value.toString()); + } catch { + // If a file, set as a file + if (value instanceof File) { + data[key] = value; + } + + // Otherwise, set as a string + // eslint-disable-next-line @typescript-eslint/no-base-to-string + data[key] = value.toString(); + } + } + + return data as T; + } + } + + if ([...query.entries()].length > 0) { + const data: Record = {}; + + for (const [key, value] of query.entries()) { + data[key] = value.toString(); + } + + return data as T; + } + + return {}; +}