From e952583c4cc1f766585b306e29efe7503b856c7b Mon Sep 17 00:00:00 2001 From: Brandon Hurrington Date: Mon, 26 Jan 2026 14:11:14 +0100 Subject: [PATCH 1/3] chore: add validation to opensearch handler --- server/api/opensearch/suggestions.get.ts | 34 +++++++++++++++--------- shared/schemas/package.ts | 9 +++++++ shared/utils/constants.ts | 2 ++ 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/server/api/opensearch/suggestions.get.ts b/server/api/opensearch/suggestions.get.ts index 335728405..4c7146e58 100644 --- a/server/api/opensearch/suggestions.get.ts +++ b/server/api/opensearch/suggestions.get.ts @@ -1,27 +1,37 @@ -import type { NpmSearchResponse } from '#shared/types' -import { NPM_REGISTRY } from '#shared/utils/constants' +import * as v from 'valibot' +import { SearchQuerySchema } from '#shared/schemas/package' +import { CACHE_MAX_AGE_ONE_MINUTE, NPM_REGISTRY } from '#shared/utils/constants' export default defineCachedEventHandler( async event => { const query = getQuery(event) - const q = String(query.q || '').trim() - if (!q) { - return [q, []] - } + try { + const q = v.parse(SearchQuerySchema, query.q) + + if (!q) { + return [q, []] + } - const params = new URLSearchParams({ text: q, size: '10' }) - const response = await $fetch(`${NPM_REGISTRY}/-/v1/search?${params}`) + const params = new URLSearchParams({ text: q, size: '10' }) + const response = await $fetch(`${NPM_REGISTRY}/-/v1/search?${params}`) - const suggestions = response.objects.map(obj => obj.package.name) - return [q, suggestions] + const suggestions = response.objects.map(obj => obj.package.name) + return [q, suggestions] + } catch (error: unknown) { + handleApiError(error, { + statusCode: 502, + message: ERROR_SUGGESTIONS_FETCH_FAILED, + }) + } }, { - maxAge: 60, + maxAge: CACHE_MAX_AGE_ONE_MINUTE, swr: true, getKey: event => { const query = getQuery(event) - return `opensearch-suggestions:${query.q || ''}` + const q = String(query.q || '').trim() + return `opensearch-suggestions:${q}` }, }, ) diff --git a/shared/schemas/package.ts b/shared/schemas/package.ts index d4d9fc730..386d16106 100644 --- a/shared/schemas/package.ts +++ b/shared/schemas/package.ts @@ -35,6 +35,15 @@ export const FilePathSchema = v.pipe( v.check(input => !input.startsWith('/'), 'Invalid path: must be relative to package root'), ) +/** + * Schema for search queries, limits length to guard against DoS attacks + */ +export const SearchQuerySchema = v.pipe( + v.string(), + v.trim(), + v.maxLength(100, 'Search query is too long'), +) + /** * Schema for package fetching where version is not required */ diff --git a/shared/utils/constants.ts b/shared/utils/constants.ts index 5ec5f4920..e4efd422f 100644 --- a/shared/utils/constants.ts +++ b/shared/utils/constants.ts @@ -1,4 +1,5 @@ // Duration +export const CACHE_MAX_AGE_ONE_MINUTE = 60 export const CACHE_MAX_AGE_ONE_HOUR = 60 * 60 export const CACHE_MAX_AGE_ONE_DAY = 60 * 60 * 24 export const CACHE_MAX_AGE_ONE_YEAR = 60 * 60 * 24 * 365 @@ -14,3 +15,4 @@ export const ERROR_CALC_INSTALL_SIZE_FAILED = 'Failed to calculate install size. export const NPM_MISSING_README_SENTINEL = 'ERROR: No README data found!' export const ERROR_JSR_FETCH_FAILED = 'Failed to fetch package from JSR registry.' export const ERROR_NPM_FETCH_FAILED = 'Failed to fetch package from npm registry.' +export const ERROR_SUGGESTIONS_FETCH_FAILED = 'Failed to fetch suggestions.' From ac151cc9ad2c2ed8110675db1e5a606b222fc1fd Mon Sep 17 00:00:00 2001 From: Brandon Hurrington Date: Mon, 2 Feb 2026 16:21:47 +0100 Subject: [PATCH 2/3] feat: scaffolding of the Atmosphere docs page and integrations section --- docs/content/4. integrations/.navigation.yml | 2 + docs/content/4. integrations/1.atmosphere.md | 89 ++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 docs/content/4. integrations/.navigation.yml create mode 100644 docs/content/4. integrations/1.atmosphere.md diff --git a/docs/content/4. integrations/.navigation.yml b/docs/content/4. integrations/.navigation.yml new file mode 100644 index 000000000..d265c8047 --- /dev/null +++ b/docs/content/4. integrations/.navigation.yml @@ -0,0 +1,2 @@ +title: Atmosphere Apps +icon: i-lucide-cable diff --git a/docs/content/4. integrations/1.atmosphere.md b/docs/content/4. integrations/1.atmosphere.md new file mode 100644 index 000000000..c04a062bc --- /dev/null +++ b/docs/content/4. integrations/1.atmosphere.md @@ -0,0 +1,89 @@ +--- +title: Atmosphere +description: npmx.dev's role in the Atmosphere +navigation: + icon: i-lucide-cloudy +--- + + + + +## Overview + +_npmx.dev_ site architecture as it pertains to the ATProtocol ecosystem, the [Atmosphere](https://atproto.com/). + +--- + +## Atmosphere + +The Atmosphere is the interconnected web of applications that all run on the ATProtocol. The primary benefit of this integration for nomx.dev are social features and connecting to the tens of millions of users ATProtocol on the social graph. Per the docs site: + +> The AT Protocol is an open, decentralized network for building social applications. + +--- + +## Architecture + +These are the components that allow for _npmx.dev_ to exist as an app on the Atmosphere: + +1. **Domain** - _npmx.dev_ site +2. **OAuth** - Required for interacting with the Atmosphere +3. **Constellation** - A global atproto backlink index +4. **Lexicons** - Schema definition language +5. **Standard Site Sync** - Synchronizes app data with PDS +6. **Bluesky Social Graph** - Consume Bluesky posts/comments/threads + +### Domain + +_npmx.dev_ site which serves as a third-party browser prioritized around high DX and native-like performance... + +### OAuth + +Necessary to ensure identities are observed... + +> OAuth is the primary mechanism in atproto for clients to make authorized requests to PDS instances. + +https://atproto.com/specs/oauth + +### Constellation + +Orchestration API layer that collects...stuff... + +### Lexicons + +Orchestration API layer that collects...stuff... + +> Lexicon is a schema definition language used to describe atproto records, HTTP endpoints (XRPC), and event stream messages. + +https://atproto.com/specs/lexicon + +### Standard Site Sync + +Synchronizes data changes from npmx.dev in the form of blog post MD files... + +### Bluesky Social Graph + +Collects and generates events to utilize Posts/Comments/Threads + +--- + +## How & Why the Atmosphere + +These are the components that allow for _npmx.dev_ to exist as an app on the Atmosphere: + +1. **Blog Posts** - Share short and long form thought from the people behind the app +2. **Comments** - Allows people to share their thoughts and feelings +3. **Awareness** - Raises brand awareness +4. **Engagement** - Promotes brand engagement + +--- + +## Resources + +These are the components that allow for _npmx.dev_ to exist as an app on the Atmosphere: + +- **ATProtocol Docs** - [Introduction](https://atproto.com) +- **Constellation Repo** - [readme.md](https://github.com/at-microcosm/microcosm-rs/tree/main/constellation) +- **Bluesky** - [Disocover Page](https://bsky.app/) + +--- From 869ab17a4f49cfe1484989eda8b0bc325b56d1fb Mon Sep 17 00:00:00 2001 From: Brandon Hurrington Date: Mon, 2 Feb 2026 16:26:43 +0100 Subject: [PATCH 3/3] fix: corrected the nav title --- docs/content/4. integrations/.navigation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/4. integrations/.navigation.yml b/docs/content/4. integrations/.navigation.yml index d265c8047..f2626af07 100644 --- a/docs/content/4. integrations/.navigation.yml +++ b/docs/content/4. integrations/.navigation.yml @@ -1,2 +1,2 @@ -title: Atmosphere Apps +title: Integrations icon: i-lucide-cable