Skip to content

Commit

Permalink
1.0 release (#14)
Browse files Browse the repository at this point in the history
* remove tldjs

* refactor firefox cookies

* refactor chrome

* Update ChromeMacosCookieProvider.ts

* lint

* remove getIterations

* refactor

* Create ChromeMacosCookieProvider.unit.test.ts

* Update ChromeMacosCookieProvider.unit.test.ts

* Create ChromeWindowsCookieProvider.unit.test.ts

* bump version to 1.0, add homepage link

* Update package.json

* 1.0 docs (#17)

* docs update

* Create browser_profiles.md

* Update index.md

* Create favicon.ico
  • Loading branch information
Kalininator committed Oct 10, 2021
1 parent 713f162 commit b4c0a5b
Show file tree
Hide file tree
Showing 29 changed files with 523 additions and 308 deletions.
Binary file added docs/favicon.ico
Binary file not shown.
50 changes: 50 additions & 0 deletions docs/pages/browser_profiles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
layout: default
title: Browser profiles
nav_order: 4
description: Specifying browser profiles
permalink: /browser-profiles
---

# Browser profiles

## Choosing browser profiles

If you are utilising multiple browser profiles, you can specify which one to use in the function options.

```js
import { getCookie, listCookies, Browser } from 'cookie-thief';

await getCookie({
browser: Browser.Chrome,
cookieName: 'foo',
domain: '.github.com',
options: {
profile: 'SomeProfile',
},
});

await listCookies({
browser: Browser.Firefox,
options: {
profile: 'SomeProfile',
},
});
```

## Listing browser profiles

If you want to programmatically list browser profiles, you can with `listProfiles`.

```js
import { listProfiles, Browser } from 'cookie-thief';

const profiles: string[] = await listProfiles(Browser.Chrome);
```

## Default browser profiles

If you do not specify a profile, a default profile name will be used.

* Firefox: `default-release`
* Chrome: `Default`
28 changes: 28 additions & 0 deletions docs/pages/compatibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
layout: default
title: Compatibility
nav_order: 2
description: Compatible browsers and operation systems
permalink: /compatibility
---

## Compatibility

### Supported Browsers

* Google Chrome
* Firefox

### Supported Operating Systems

* MacOS
* Linux
* Windows

## Limitations

### MacOS

On macOS, this package requires keychain access to access the Google Chrome encryption key.
You will get a dialogue popup requesting access.
Due to this popup, you cannot use this library completely headlessly to fetch cookies unless you run it once and click `Always Allow`.
55 changes: 55 additions & 0 deletions docs/pages/fetching_cookies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
layout: default
title: Fetching Cookies
nav_order: 3
description: Getting and listing cookies
permalink: /fetching-cookies
---

# Get Cookie

`getCookie` can be used to try and find a cookie based on the domain and cookie name.
If a cookie is not found, the result will be `undefined`.

```js
import { getCookie, Browser } from 'cookie-thief';

const cookie = await getCookie({
browser: Browser.Chrome, // or Browser.Firefox
domain: '.reddit.com',
cookieName: 'loid',
});

/*
{
name: 'loid',
value: 'the decrypted cookie value here',
domain: '.reddit.com',
path: '/',
}
*/
```

# List Cookies

`listCookies` can be used to list all cookies for a browser.
If no cookies are found you will get `[]`.

```js
import { listCookies, Browser } from 'cookie-thief';

const cookies = await listCookies({
browser: Browser.Chrome, // or Browser.Firefox
})

/*
[
{
name: 'loid',
value: 'the decrypted cookie value here',
domain: '.reddit.com',
path: '/',
}
]
*/
```
72 changes: 19 additions & 53 deletions docs/pages/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,15 @@ permalink: /
---

# Cookie Thief
{: .no_toc}

1. TOC
{:toc}
![npm](https://img.shields.io/npm/v/cookie-thief)
![npm](https://img.shields.io/npm/dw/cookie-thief)
![npm bundle size](https://img.shields.io/bundlephobia/min/cookie-thief)
![npm bundle size](https://img.shields.io/bundlephobia/minzip/cookie-thief)
[![codecov](https://codecov.io/gh/Kalininator/cookie-thief/branch/master/graph/badge.svg?token=H0F1TIE0CY)](https://codecov.io/gh/Kalininator/cookie-thief)

## Compatibility

Currently supports only Google Chrome and Firefox on MacOS, Linux, and Windows.

In the future will hopefully expand to support other browsers.
A node.js library for extracting cookies from a browser installed on your system.
Inspired by [chrome-cookies-secure](https://github.com/bertrandom/chrome-cookies-secure).

## Installation

Expand All @@ -31,23 +30,24 @@ yarn add cookie-thief

## Usage

### Google Chrome

```javascript
const { getCookie, listCookies, Browser } = require('cookie-thief')
const { getCookie, listCookies, Browser, listBrowsers } = require('cookie-thief')

// Get a cookie from chrome browser for domain .github.com, searching for cookie named 'dotcom_user'
const cookie = await getCookie({
browser: Browser.Chrome,
url: 'https://github.com',
domain: '.github.com',
cookieName: 'dotcom_user',
options: {
profile: 'Default',
},
});
console.log(cookie);
// Will be a string if cookie is successfully found
// Will be a Cookie if cookie is successfully found
// Will be undefined if not found
//{
// name: 'cookie name here',
// value: 'decrypted cookie content here',
// host: 'hostname of cookie here',
// path: 'path of cookie here'
//}

const cookies = await listCookies({
browser: Browser.Chrome,
Expand All @@ -63,42 +63,8 @@ console.log(cookies);
// }
//]

```

### Firefox

```javascript
const { getCookie, Browser } = require('cookie-thief')

// Get a cookie from chrome browser for domain .github.com, searching for cookie named 'dotcom_user'
const cookie = await getCookie({
browser: Browser.Firefox,
url: 'https://github.com',
cookieName: 'dotcom_user',
options: {
profile: 'default-release',
},
});
console.log(cookie);
// Will be a string if cookie is successfully found
// Will be undefined if not found
const browsers = listBrowsers();
console.log(browsers);
// [ Browser.Chrome, Browser.Firefox ]

const cookies = await listCookies({
browser: Browser.Firefox,
});
console.log(cookies);
// Array of cookies
//[
// {
// name: 'cookie name here',
// value: 'decrypted cookie content here',
// host: 'hostname of cookie here',
// path: 'path of cookie here'
// }
//]
```

## Limitations

### macOS
On macOS, this package requires keychain access to access the Google Chrome encryption key. You will get a dialogue popup requesting access.
13 changes: 8 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
{
"name": "cookie-thief",
"version": "0.7.0",
"version": "1.0.0",
"description": "Steal browser cookies",
"main": "./lib/index.js",
"author": "Alex Kalinin (https://kalinin.uk)",
"homepage": "https://kalininator.github.io/cookie-thief/",
"bugs": {
"url": "https://github.com/Kalininator/cookie-thief/issues"
},
"license": "MIT",
"files": [
"lib/",
Expand Down Expand Up @@ -33,8 +37,8 @@
"publish-package": "npm run build && npm publish",
"test": "jest",
"test:watch": "jest --watch",
"clean:some": "rm -rf ./lib ./docs",
"clean:all": "rm -rf ./node_modules ./package-lock.json ./lib ./docs",
"clean:some": "rm -rf ./lib",
"clean:all": "rm -rf ./node_modules ./package-lock.json ./lib",
"pr:lint": "./node_modules/eslint/bin/eslint.js 'src/**/*.ts'",
"pr:test": "jest",
"t": "ts-node testFile.ts"
Expand Down Expand Up @@ -67,8 +71,7 @@
},
"dependencies": {
"better-sqlite3": "^7.4.3",
"ini": "^2.0.0",
"tldjs": "^2.3.1"
"ini": "^2.0.0"
},
"optionalDependencies": {
"keytar": "^7.7.0",
Expand Down
6 changes: 6 additions & 0 deletions src/CookieProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Cookie } from './types';

export interface CookieProvider {
getCookie(domain: string, cookieName: string): Promise<Cookie | undefined>;
listCookies(): Promise<Cookie[]>;
}
4 changes: 2 additions & 2 deletions src/chrome/ChromeCookieDatabase.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('ChromeCookieDatabase', () => {
});
expect(getFn).toHaveBeenCalled();
expect(prepareFn).toHaveBeenCalledWith(
`SELECT host_key, path, is_secure, expires_utc, name, value, encrypted_value, creation_utc, is_httponly, has_expires, is_persistent FROM cookies where host_key like '%.domain.com' and name like '%someCookie' ORDER BY LENGTH(path) DESC, creation_utc ASC`,
`SELECT host_key, path, name, encrypted_value FROM cookies where host_key like '%.domain.com' and name like '%someCookie' ORDER BY LENGTH(path) DESC, creation_utc ASC`,
);
expect(sqlite as unknown as jest.Mock).toHaveBeenCalledWith(path, {
readonly: true,
Expand Down Expand Up @@ -68,7 +68,7 @@ describe('ChromeCookieDatabase', () => {
]);
expect(allFn).toHaveBeenCalled();
expect(prepareFn).toHaveBeenCalledWith(
`SELECT host_key, path, is_secure, expires_utc, name, value, encrypted_value, creation_utc, is_httponly, has_expires, is_persistent FROM cookies`,
`SELECT host_key, path, name, encrypted_value FROM cookies`,
);
expect(sqlite as unknown as jest.Mock).toHaveBeenCalledWith(path, {
readonly: true,
Expand Down
31 changes: 6 additions & 25 deletions src/chrome/ChromeCookieDatabase.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,21 @@
import sqlite from 'better-sqlite3';
import { ChromeCookie, ChromeCookieRepository } from './ChromeCookieRepository';

type BooleanNumber = 0 | 1;
export class ChromeCookieDatabase implements ChromeCookieRepository {
constructor(private path: string) {}

export type ChromeCookie = {
host_key: string;
path: string;
is_secure: BooleanNumber;
expires_utc: number;
name: string;
value: string;
encrypted_value: Buffer;
creation_utc: number;
is_httponly: BooleanNumber;
has_expires: BooleanNumber;
is_persistent: BooleanNumber;
};

export class ChromeCookieDatabase {
path: string;

constructor(path: string) {
this.path = path;
}

findCookie(cookieName: string, domain: string): ChromeCookie {
findCookie(cookieName: string, domain: string): ChromeCookie | undefined {
const db = sqlite(this.path, { readonly: true, fileMustExist: true });
const statement = db.prepare(
`SELECT host_key, path, is_secure, expires_utc, name, value, encrypted_value, creation_utc, is_httponly, has_expires, is_persistent FROM cookies where host_key like '%${domain}' and name like '%${cookieName}' ORDER BY LENGTH(path) DESC, creation_utc ASC`,
`SELECT host_key, path, name, encrypted_value FROM cookies where host_key like '%${domain}' and name like '%${cookieName}' ORDER BY LENGTH(path) DESC, creation_utc ASC`,
);
return statement.get();
}

listCookies(): ChromeCookie[] {
const db = sqlite(this.path, { readonly: true, fileMustExist: true });
const statement = db.prepare(
`SELECT host_key, path, is_secure, expires_utc, name, value, encrypted_value, creation_utc, is_httponly, has_expires, is_persistent FROM cookies`,
`SELECT host_key, path, name, encrypted_value FROM cookies`,
);
const cookies: ChromeCookie[] = statement.all();
return cookies;
Expand Down
11 changes: 11 additions & 0 deletions src/chrome/ChromeCookieRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type ChromeCookie = {
host_key: string;
path: string;
name: string;
encrypted_value: Buffer;
};

export interface ChromeCookieRepository {
findCookie(cookieName: string, domain: string): ChromeCookie | undefined;
listCookies(): ChromeCookie[];
}
39 changes: 39 additions & 0 deletions src/chrome/ChromeLinuxCookieProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { CookieProvider } from '../CookieProvider';
import { Cookie } from '../types';
import { ChromeCookie, ChromeCookieRepository } from './ChromeCookieRepository';
import { decrypt } from './decrypt';
import { getLinuxDerivedKey } from './getDerivedKey';

const KEYLENGTH = 16;
const ITERATIONS = 1;

async function decryptCookie(cookie: ChromeCookie): Promise<string> {
const derivedKey = await getLinuxDerivedKey(KEYLENGTH, ITERATIONS);
return decrypt(derivedKey, cookie.encrypted_value, KEYLENGTH);
}

async function toCookie(chromeCookie: ChromeCookie): Promise<Cookie> {
return {
value: await decryptCookie(chromeCookie),
host: chromeCookie.host_key,
path: chromeCookie.path,
name: chromeCookie.name,
};
}

export class ChromeLinuxCookieProvider implements CookieProvider {
constructor(private db: ChromeCookieRepository) {}

async getCookie(
domain: string,
cookieName: string,
): Promise<Cookie | undefined> {
const chromeCookie = this.db.findCookie(cookieName, domain);
if (!chromeCookie) return undefined;
return toCookie(chromeCookie);
}

async listCookies(): Promise<Cookie[]> {
return Promise.all(this.db.listCookies().map(toCookie));
}
}

0 comments on commit b4c0a5b

Please sign in to comment.