Skip to content

Commit

Permalink
feat: implement Contact to replace @nodesecure/authors
Browse files Browse the repository at this point in the history
  • Loading branch information
fraxken committed Jul 8, 2024
1 parent f626feb commit 596fbf3
Show file tree
Hide file tree
Showing 25 changed files with 639 additions and 56 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ Click on one of the links to access the documentation of the workspace:
| tarball | [@nodesecure/sec-literal](./workspaces/tarball) |
| tree-walker | [@nodesecure/tree-walker](./workspaces/tree-walker) |
| mama | [@nodesecure/mama](./workspaces/mama) |
| contact | [@nodesecure/contact](./workspaces/contact) |
| conformance | [@nodesecure/npm-types](./workspaces/conformance) |
| npm-types | [@nodesecure/npm-types](./workspaces/npm-types) |

Expand Down
8 changes: 3 additions & 5 deletions docs/from.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,18 +141,16 @@ await hydratePayloadDependencies(dependencies, {
For more information on how to create a strategy and how they operate, please [read the following documentation](https://github.com/NodeSecure/vulnera/blob/main/docs/adding_new_strategy.md).
## Steps 4: Get warnings and flagged authors
## Steps 4: Get warnings and wanted contacts
In this step, we look for packages or authors potentially identified as problematic (by the person requesting the analysis or us).
```js
const { warnings, flaggedAuthors } = await getDependenciesWarnings(dependencies);
const { warnings, wanteds } = await getDependenciesWarnings(dependencies);
payload.warnings = warnings;
payload.flaggedAuthors = flaggedAuthors;
payload.wanteds = wanteds;
```
> Under the hood we use our newest package [@nodesecure/authors](https://github.com/NodeSecure/authors) to optimize and flag authors.
By default we show warnings for the following two packages:
- @scarf/scarf
- iohook
Expand Down
43 changes: 31 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"workspaces/mama",
"workspaces/tree-walker",
"workspaces/conformance",
"workspaces/contact",
"workspaces/npm-types"
],
"scripts": {
Expand Down
3 changes: 3 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
{
"path": "./workspaces/mama"
},
{
"path": "./workspaces/contact"
},
{
"path": "./workspaces/tarball"
},
Expand Down
105 changes: 105 additions & 0 deletions workspaces/contact/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<p align="center"><h1 align="center">
@nodesecure/contact
</h1>

<p align="center">
Utilities to extract/fetch data on NPM contacts (author, maintainers etc..)
</p>

## Requirements
- [Node.js](https://nodejs.org/en/) v20 or higher

## Getting Started

This package is available in the Node Package Repository and can be easily installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or [yarn](https://yarnpkg.com).

```bash
$ npm i @nodesecure/contact
# or
$ yarn add @nodesecure/contact
```

## Usage example

Here is an example of usage from the Scanner. In this case, we are using **dependenciesMap**, which is a `Record<string, Dependency>`. However, you can build your own record of `ContactExtractorPackageMetadata`.

```ts
import {
ContactExtractor,
type ContactExtractorPackageMetadata
} from "@nodesecure/contact";

const dependencies: Record<string, ContactExtractorPackageMetadata> = Object.create(null);
for (const [packageName, dependency] of dependenciesMap) {
const { author, maintainers } = dependency.metadata;

dependencies[packageName] = {
maintainers,
...( author === null ? {} : { author } )
}
}

const extractor = new ContactExtractor({
wanted: [
{
name: "Sindre Sorhus"
}
]
});
const wanteds = extractor.fromDependencies(
dependencies
);
console.log(wanteds);
```

## API

Contact is defined by the following TypeScript interface:
```ts
interface Contact {
email?: string;
url?: string;
name: string;
}
```

### ContactExtractor

The constructor take a list of contacts you want to find.

```ts
interface ContactExtractorOptions {
wanted: Contact[];
}
```

The method **fromDependencies** will return an array of WantedContact objects if any are found in the provided dependencies.

```ts
export type WantedContact = Contact & {
dependencies: string[];
}
```
### compareContact(contactA: Contact, contactB: Contact): boolean
Compare two contacts and return `true` if they are the same person
```ts
import {
compareContact
} from "@nodesecure/contact";
import assert from "node:assert";

assert.ok(
compareContact(
{ name: "john doe" },
{ name: "John Doe" }
)
);
```

Each string is trimmed, converted to lowercase, and any multiple spaces are reduced to a single space.

## License
MIT
38 changes: 38 additions & 0 deletions workspaces/contact/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "@nodesecure/contact",
"version": "1.0.0",
"description": "Utilities to extract/fetch data on NPM contacts (author, maintainers ..)",
"type": "module",
"exports": "./dist/index.js",
"types": "./dist/index.d.ts",
"engines": {
"node": ">=20"
},
"scripts": {
"build": "tsc -b",
"prepublishOnly": "npm run build",
"test-only": "glob -c \"tsx --test\" \"./test/**/*.spec.ts\"",
"test": "c8 -r html npm run test-only"
},
"files": [
"dist"
],
"keywords": [
"author",
"contact",
"maintainer"
],
"author": "GENTILHOMME Thomas <gentilhomme.thomas@gmail.com>",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/NodeSecure/scanner.git"
},
"bugs": {
"url": "https://github.com/NodeSecure/scanner/issues"
},
"homepage": "https://github.com/NodeSecure/tree/master/workspaces/contact#readme",
"devDependencies": {
"@faker-js/faker": "^8.4.1"
}
}
65 changes: 65 additions & 0 deletions workspaces/contact/src/ContactExtractor.class.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Import Third-party Dependencies
import type { Contact } from "@nodesecure/npm-types";

// Import Internal Dependencies
import { compareContact } from "./utils/index.js";

export interface ContactExtractorPackageMetadata {
author?: Contact;
maintainers: Contact[];
}

export interface ContactExtractorOptions {
wanted: Contact[];
}

export type WantedContact = Contact & {
dependencies: string[];
}

export class ContactExtractor {
private pinnedWanted: Contact[] = [];

constructor(options: ContactExtractorOptions) {
const { wanted } = options;

this.pinnedWanted = structuredClone(wanted);
}

private getFreshWantedContacts(): WantedContact[] {
return structuredClone(this.pinnedWanted)
.map((wanted) => ({ ...wanted, dependencies: [] }))
}

private isWantedMatchingPackageMetadata(
wanted: WantedContact,
metadata: ContactExtractorPackageMetadata
): boolean {
return [
...(metadata.author ? [metadata.author] : []),
...metadata.maintainers
].some((contact) => compareContact(wanted, contact));
}

fromDependencies(
dependencies: Record<string, ContactExtractorPackageMetadata>
): WantedContact[] {
const wanteds = this.getFreshWantedContacts();

for (const [packageName, metadata] of Object.entries(dependencies)) {
for (const wanted of wanteds) {
const isMatching = this.isWantedMatchingPackageMetadata(
wanted,
metadata
);

if (isMatching) {
wanted.dependencies.push(packageName);
}
}
}

return wanteds
.filter((wanted) => wanted.dependencies.length > 0);
}
}
4 changes: 4 additions & 0 deletions workspaces/contact/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./ContactExtractor.class.js";
export {
compareContact
} from "./utils/index.js";
Loading

0 comments on commit 596fbf3

Please sign in to comment.