diff --git a/.all-contributorsrc b/.all-contributorsrc new file mode 100644 index 0000000..3acba6e --- /dev/null +++ b/.all-contributorsrc @@ -0,0 +1,15 @@ +{ + "files": [ + "README.md" + ], + "imageSize": 100, + "commit": false, + "contributors": [], + "contributorsPerLine": 7, + "projectName": "rc", + "projectOwner": "NodeSecure", + "repoType": "database", + "repoHost": "https://github.com", + "skipCi": true, + "commitConvention": "angular" +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..4cb1507 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true +end_of_line = lf + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..e69de29 diff --git a/.gitignore b/.gitignore index 6704566..cf658a5 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,5 @@ dist # TernJS port file .tern-port + +temp/ diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..43c97e7 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..20cf6b9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,46 @@ +# Contributing to NodeSecure + +Contributions to NodeSecure include code, documentation, answering user questions and +running the project's infrastructure + +The NodeSecure project welcomes all contributions from anyone willing to work in +good faith with other contributors and the community. No contribution is too +small and all contributions are valued. + +This guide explains the process for contributing to the NodeSecure project's. + +## [Code of Conduct](https://github.com/NodeSecure/Governance/blob/main/CODE_OF_CONDUCT.md) + +The NodeSecure project has a +[Code of Conduct](https://github.com/NodeSecure/Governance/blob/main/CODE_OF_CONDUCT.md) +that *all* contributors are expected to follow. This code describes the +*minimum* behavior expectations for all contributors. + +See [details on our policy on Code of Conduct](https://github.com/NodeSecure/Governance/blob/main/COC_POLICY.md). + + +## Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +* (a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +* (b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +* (c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +* (d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. diff --git a/README.md b/README.md index ee28bc8..ac35ead 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,25 @@ # database NodeSecure Security Database + +## Contributors ✨ + + +[![All Contributors](https://img.shields.io/badge/all_contributors-4-orange.svg?style=flat-square)](#contributors-) + + +Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): + + + + + + + + + + + +## License + +MIT + diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..3700d62 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,5 @@ +# Reporting Security Issues + +To report a security issue, please [publish a private security advisory](https://github.com/NodeSecure/rc/database/advisories) with a description of the issue, the steps you took to create the issue, affected versions, and, if known, mitigations for the issue. + +Our vulnerability management team will respond within one week. If the issue is confirmed as a vulnerability, we will open a Security Advisory and acknowledge your contributions as part of it. This project follows a 90 day disclosure timeline. diff --git a/package.json b/package.json new file mode 100644 index 0000000..b0d4ef6 --- /dev/null +++ b/package.json @@ -0,0 +1,42 @@ +{ + "name": "@nodesecure/database", + "version": "1.0.0", + "description": "NodeSecure Security Database", + "main": "index.js", + "type": "module", + "scripts": { + "build": "tsc", + "start:http": "node --env-file=.env ./dist/api/server.js", + "test": "glob -c \"tsx --test\" \"./test/**/*.spec.ts\"", + "coverage": "c8 -r html npm test" + }, + "engines": { + "node": "=>20" + }, + "keywords": [ + "database", + "npm", + "registry", + "secure", + "security" + ], + "author": "GENTILHOMME Thomas ", + "license": "MIT", + "devDependencies": { + "@nodesecure/eslint-config": "^1.9.0", + "@types/node": "^20.12.11", + "c8": "^9.1.0", + "eslint": "^8.57.0", + "glob": "^10.3.12", + "tsx": "^4.9.3", + "typescript": "^5.4.5" + }, + "dependencies": { + "@nodesecure/npm-registry-sdk": "^2.1.1", + "fastify": "^4.27.0", + "fastify-plugin": "^4.5.1", + "pino": "^9.0.0", + "pino-pretty": "^11.0.0", + "zod": "^3.23.8" + } +} diff --git a/src/api/app.ts b/src/api/app.ts new file mode 100644 index 0000000..b4bb9b5 --- /dev/null +++ b/src/api/app.ts @@ -0,0 +1,37 @@ +// Import Third-party Dependencies +import fastify, { FastifyInstance } from "fastify"; + +// Import Internal Dependencies +import * as endpoints from "./endpoints/index.js"; + +// Import Plugins +import { npmAuthenticationPlugin } from "./plugins/npmAuthentication.js"; +import { standardRegistryPlugin } from "./plugins/registry.js"; + +export function createServer(): FastifyInstance { + const server = fastify({ + logger: { + transport: { + target: "pino-pretty" + } + } + }); + server.register(npmRegistryEndpoints); + + return server; +} + +/** + * @see https://github.com/npm/registry/blob/main/docs/REGISTRY-API.md#endpoints + */ +async function npmRegistryEndpoints( + server: FastifyInstance +) { + server.register(standardRegistryPlugin); + server.register(npmAuthenticationPlugin); + + server.get("/", endpoints.metadata); + server.get("/:package", endpoints.packument); + server.get("/:package/:version", endpoints.packumentVersion); + server.get("/-/v1/search", endpoints.search); +} diff --git a/src/api/endpoints/index.ts b/src/api/endpoints/index.ts new file mode 100644 index 0000000..9bfcf61 --- /dev/null +++ b/src/api/endpoints/index.ts @@ -0,0 +1,4 @@ +export * from "./metadata.js"; +export * from "./package.js"; +export * from "./packageWithVersion.js"; +export * from "./search.js"; diff --git a/src/api/endpoints/metadata.ts b/src/api/endpoints/metadata.ts new file mode 100644 index 0000000..bf68498 --- /dev/null +++ b/src/api/endpoints/metadata.ts @@ -0,0 +1,23 @@ +// Import Third-party Dependencies +import { NpmRegistryMetadata } from "@nodesecure/npm-registry-sdk"; + +/** + * TODO: rework this with real metadata + * - Maybe return a Partial + * and remove useless keys! (not sure what's the impact). + */ +export async function metadata(): Promise { + return { + db_name: "nodesecure_db", + doc_count: 0, + doc_del_count: 0, + update_seq: 0, + purge_seq: 0, + compact_running: false, + disk_size: 0, + data_size: 0, + instance_start_time: process.uptime().toString(), + disk_format_version: 6, + committed_update_seq: 0 + } +} diff --git a/src/api/endpoints/package.ts b/src/api/endpoints/package.ts new file mode 100644 index 0000000..6ea5bda --- /dev/null +++ b/src/api/endpoints/package.ts @@ -0,0 +1,19 @@ +// Import Third-party Dependencies +import { FastifyRequest } from "fastify"; + +interface PackumentEndpoint { + Params: { + package: string; + } +} + +export async function packument( + request: FastifyRequest +) { + const data = await request.server.registry.package( + request.params.package, + request.npmAuthenticationOptions + ); + + return data; +} diff --git a/src/api/endpoints/packageWithVersion.ts b/src/api/endpoints/packageWithVersion.ts new file mode 100644 index 0000000..b48dd58 --- /dev/null +++ b/src/api/endpoints/packageWithVersion.ts @@ -0,0 +1,21 @@ +// Import Third-party Dependencies +import { FastifyRequest } from "fastify"; + +interface PackumentVersionEndpoint { + Params: { + package: string; + version: string; + } +} + +export async function packumentVersion( + request: FastifyRequest +) { + const data = await request.server.registry.packageWithVersion( + request.params.package, + request.params.version, + request.npmAuthenticationOptions + ); + + return data; +} diff --git a/src/api/endpoints/search.ts b/src/api/endpoints/search.ts new file mode 100644 index 0000000..a00a0c2 --- /dev/null +++ b/src/api/endpoints/search.ts @@ -0,0 +1,18 @@ +// Import Third-party Dependencies +import { FastifyRequest } from "fastify"; +import { SearchCriteria } from "@nodesecure/npm-registry-sdk"; + +interface SearchEndpoint { + Querystring: SearchCriteria; +} + +export async function search( + request: FastifyRequest +) { + const data = await request.server.registry.search( + request.query, + request.npmAuthenticationOptions + ); + + return data; +} diff --git a/src/api/plugins/npmAuthentication.ts b/src/api/plugins/npmAuthentication.ts new file mode 100644 index 0000000..89c2da4 --- /dev/null +++ b/src/api/plugins/npmAuthentication.ts @@ -0,0 +1,41 @@ +// Import Third-party Dependencies +import { + FastifyInstance, + FastifyRequest, + FastifyPluginAsync +} from "fastify"; +import fp from "fastify-plugin"; + +// Import Internal Dependencies +import { NpmAuthenticationOptions } from "../services/NpmRegistry.js"; + +// CONSTANTS +const kNpmTokenHeaderName = "x-npm-token"; + +async function npmAuthenticationPrehandler( + request: FastifyRequest +) { + const token = request.headers[kNpmTokenHeaderName]; + if (typeof token === "string") { + request.npmAuthenticationOptions = { + token + }; + } +} + +async function authentication( + server: FastifyInstance +) { + server.addHook("preHandler", npmAuthenticationPrehandler); +} + +export const npmAuthenticationPlugin: FastifyPluginAsync = fp( + authentication, + { name: "npmAuthentication" } +); + +declare module "fastify" { + interface FastifyRequest { + npmAuthenticationOptions?: NpmAuthenticationOptions; + } +} diff --git a/src/api/plugins/registry.ts b/src/api/plugins/registry.ts new file mode 100644 index 0000000..3550098 --- /dev/null +++ b/src/api/plugins/registry.ts @@ -0,0 +1,22 @@ +// Import Third-party Dependencies +import { + FastifyInstance, + FastifyPluginAsync +} from "fastify"; +import fp from "fastify-plugin"; + +// Import Internal Dependencies +import { StandardRegistry, NpmRegistry } from "../services/NpmRegistry.js"; + +export const standardRegistryPlugin: FastifyPluginAsync = fp( + async function(server: FastifyInstance) { + server.decorate("registry", new NpmRegistry()); + }, + { name: "registry" } +); + +declare module "fastify" { + interface FastifyInstance { + registry: StandardRegistry; + } +} diff --git a/src/api/server.ts b/src/api/server.ts new file mode 100644 index 0000000..e93d444 --- /dev/null +++ b/src/api/server.ts @@ -0,0 +1,23 @@ +// Import Third-party Dependencies +import z from "zod"; + +// Import Internal Dependencies +import { createServer } from "./app.js"; + +// CONSTANTS +const kServerEnvSchema = z.object({ + host: z.string().optional(), + port: z.coerce.number().optional().default(0) +}); + +const env = kServerEnvSchema.parse(process.env); +const server = createServer(); + +server.listen(env, function httpListeningCallback(err, addr) { + if (err) { + server.log.error(err); + process.exit(1); + } + + server.log.info(`Server listening on ${addr}`); +}); diff --git a/src/api/services/NpmRegistry.ts b/src/api/services/NpmRegistry.ts new file mode 100644 index 0000000..9b4f3da --- /dev/null +++ b/src/api/services/NpmRegistry.ts @@ -0,0 +1,44 @@ +// Import Third-party Dependencies +import { + packument, + packumentVersion, + search, + + type PackumentOptions, + type Packument, + type PackumentVersion, + type SearchCriteria, + type SearchResult +} from "@nodesecure/npm-registry-sdk"; + +export interface StandardRegistry { + package: (name: string, options?: NpmAuthenticationOptions) => Promise; + packageWithVersion: (name: string, version: string, options?: NpmAuthenticationOptions) => Promise; + search: (criteria: SearchCriteria, options?: NpmAuthenticationOptions) => Promise; +} + +export type NpmAuthenticationOptions = Pick; + +export class NpmRegistry implements StandardRegistry { + package( + name: string, + options?: NpmAuthenticationOptions + ) { + return packument(name, options); + } + + packageWithVersion( + name: string, + version: string, + options?: NpmAuthenticationOptions + ) { + return packumentVersion(name, version, options); + } + + search( + criteria: SearchCriteria, + options?: NpmAuthenticationOptions + ) { + return search(criteria, options); + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..aae92b3 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "declaration": true, + "strictNullChecks": true, + "target": "ES2022", + "outDir": "dist", + "module": "ES2022", + "moduleResolution": "node", + "esModuleInterop": true, + "resolveJsonModule": true, + "skipDefaultLibCheck": true, + "forceConsistentCasingInFileNames": true, + "sourceMap": true, + "rootDir": "./src", + "types": ["node"] + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file