Skip to content

Commit

Permalink
chore: maintenance (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
brc-dd committed Mar 9, 2024
1 parent 742fdcd commit 34cb0c7
Show file tree
Hide file tree
Showing 23 changed files with 1,688 additions and 1,694 deletions.
9 changes: 8 additions & 1 deletion .eslintrc.yaml
Expand Up @@ -4,15 +4,18 @@ env:
extends:
- plugin:import/recommended
- plugin:import/typescript
- plugin:security/recommended
- plugin:security/recommended-legacy
- eslint:recommended
- plugin:@typescript-eslint/all
- airbnb-base
- airbnb-typescript/base
- plugin:unicorn/all
- plugin:prettier/recommended
ignorePatterns:
- dist
- tests/deno
- tests/bun/env.d.ts
- tsup.config.ts
parser: '@typescript-eslint/parser'
parserOptions:
ecmaVersion: latest
Expand All @@ -36,3 +39,7 @@ rules:
'@typescript-eslint/no-type-alias': off
'@typescript-eslint/prefer-readonly-parameter-types': off
'@typescript-eslint/max-params': off

unicorn/no-null: off
unicorn/prevent-abbreviations: off
unicorn/prefer-string-replace-all: off
12 changes: 6 additions & 6 deletions .github/workflows/ci.yaml
Expand Up @@ -20,9 +20,9 @@ jobs:
name: test (node-${{ matrix.node-version }})
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: pnpm
Expand All @@ -32,9 +32,9 @@ jobs:
name: test (deno, bun)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/publish.yaml
Expand Up @@ -12,13 +12,14 @@ jobs:
contents: read
id-token: write
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
registry-url: https://registry.npmjs.org
- run: npm publish --provenance
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- run: npx jsr publish
4 changes: 2 additions & 2 deletions .release-it.yaml
Expand Up @@ -10,5 +10,5 @@ plugins:
ignoreRecommendedBump: true
hooks:
'after:bump':
pnpm lint && pnpm test:node && sed -i '' -e 's/${latestVersion}/${version}/g' README.md && git
add -f dist README.md
"pnpm lint && pnpm test:node && sed -i '' -e 's/${latestVersion}/${version}/g' README.md
jsr.json && git add README.md jsr.json"
51 changes: 43 additions & 8 deletions README.md
@@ -1,4 +1,10 @@
# iron-webcrypto [![jsDocs.io](https://img.shields.io/badge/jsDocs.io-reference-blue?style=flat-square)](https://www.jsdocs.io/package/iron-webcrypto) [![downloads](https://img.shields.io/npm/dm/iron-webcrypto?style=flat-square)](https://www.npmjs.com/package/iron-webcrypto) [![npm](https://img.shields.io/npm/v/iron-webcrypto?style=flat-square)](https://www.npmjs.com/package/iron-webcrypto) [![deno](https://img.shields.io/badge/[email protected]?style=flat-square)](https://deno.land/x/[email protected]/mod.ts)
# iron-webcrypto

[![jsDocs.io](https://img.shields.io/badge/jsDocs.io-reference-blue?style=flat-square)](https://www.jsdocs.io/package/iron-webcrypto)
[![downloads](https://img.shields.io/npm/dm/iron-webcrypto?style=flat-square)](https://www.npmjs.com/package/iron-webcrypto)
[![npm](https://img.shields.io/npm/v/iron-webcrypto?style=flat-square)](https://www.npmjs.com/package/iron-webcrypto)
[![deno](https://img.shields.io/badge/[email protected]?style=flat-square)](https://deno.land/x/[email protected]/mod.ts)
[![jsr](https://img.shields.io/badge/jsr-@brc--dd/[email protected]?style=flat-square)](https://jsr.io/@brc-dd/iron)

This module is a replacement for `@hapi/iron`, written using standard APIs like
Web Crypto and Uint8Array, which make this compatible with a variety of runtimes
Expand All @@ -13,25 +19,50 @@ like Node.js, Deno, Bun, browsers, workers, and edge environments. Refer

## Installation

Simply run:
For Node.js and Bun, run any of the following commands depending on your package
manager / runtime:

```sh
npm add iron-webcrypto
yarn add iron-webcrypto
pnpm add iron-webcrypto
bun add iron-webcrypto
```

Change the package manager to whatever you like. On Deno and browsers, you can
use [esm.sh](https://esm.sh/) for importing this:
You can then import it using:

```ts
import * as Iron from 'https://esm.sh/[email protected]'
import * as Iron from 'iron-webcrypto'
```

If using JSR, run any of the following commands depending on your package
manager / runtime:

```sh
npx jsr add @brc-dd/iron
yarn dlx jsr add @brc-dd/iron
pnpm dlx jsr add @brc-dd/iron
bunx jsr add @brc-dd/iron
deno add @brc-dd/iron
```

You can then import it using:

```ts
import * as Iron from '@brc-dd/iron'
```

This module is also published on `deno.land/x` as `iron`:
On Deno, you can also any of the following imports:

```ts
import * as Iron from 'https://deno.land/x/[email protected]/mod.ts'
import * as Iron from 'https://esm.sh/[email protected]'
import * as Iron from 'npm:[email protected]'
```

Don't use this module directly in the browser. While it will work, it's not
recommended to use it in client-side code because of the security implications.

## Usage

Refer [`@hapi/iron`'s docs](https://hapi.dev/module/iron/). There are certain
Expand Down Expand Up @@ -95,13 +126,17 @@ thoroughly review the code.
@smithy/util-base64
Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
https://cdn.jsdelivr.net/npm/@smithy/[email protected]/LICENSE
https://cdn.jsdelivr.net/npm/@smithy/[email protected]/LICENSE
@smithy/util-utf8
Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
https://cdn.jsdelivr.net/npm/@smithy/[email protected]/LICENSE
```

## Sponsors

<p align="center">
<a href="https://cdn.jsdelivr.net/gh/brc-dd/static/sponsors.svg">
<img src='https://cdn.jsdelivr.net/gh/brc-dd/static/sponsors.svg'/>
<img alt="brc-dd's sponsors" src='https://cdn.jsdelivr.net/gh/brc-dd/static/sponsors.svg'/>
</a>
</p>
78 changes: 35 additions & 43 deletions dist/index.cjs
@@ -1,8 +1,8 @@
'use strict';

// node_modules/.pnpm/@[email protected]/node_modules/@smithy/util-base64/dist-es/constants.browser.js
// src/utils.ts
var alphabetByEncoding = {};
var alphabetByValue = new Array(64);
var alphabetByValue = Array.from({ length: 64 });
for (let i = 0, start = "A".charCodeAt(0), limit = "Z".charCodeAt(0); i + start <= limit; i++) {
const char = String.fromCharCode(i + start);
alphabetByEncoding[char] = i;
Expand All @@ -21,20 +21,25 @@ for (let i = 0; i < 10; i++) {
alphabetByEncoding[char] = index;
alphabetByValue[index] = char;
}
alphabetByEncoding["+"] = 62;
alphabetByValue[62] = "+";
alphabetByEncoding["/"] = 63;
alphabetByValue[63] = "/";
alphabetByEncoding["-"] = 62;
alphabetByValue[62] = "-";
alphabetByEncoding["_"] = 63;
alphabetByValue[63] = "_";
var bitsPerLetter = 6;
var bitsPerByte = 8;
var maxLetterValue = 63;

// node_modules/.pnpm/@[email protected]/node_modules/@smithy/util-base64/dist-es/fromBase64.browser.js
var fromBase64 = (input) => {
var stringToBuffer = (value) => {
return new TextEncoder().encode(value);
};
var bufferToString = (value) => {
return new TextDecoder().decode(value);
};
var base64urlDecode = (_input) => {
const input = _input + "=".repeat((4 - _input.length % 4) % 4);
let totalByteLength = input.length / 4 * 3;
if (input.slice(-2) === "==") {
if (input.endsWith("==")) {
totalByteLength -= 2;
} else if (input.slice(-1) === "=") {
} else if (input.endsWith("=")) {
totalByteLength--;
}
const out = new ArrayBuffer(totalByteLength);
Expand All @@ -43,14 +48,14 @@ var fromBase64 = (input) => {
let bits = 0;
let bitLength = 0;
for (let j = i, limit = i + 3; j <= limit; j++) {
if (input[j] !== "=") {
if (input[j] === "=") {
bits >>= bitsPerLetter;
} else {
if (!(input[j] in alphabetByEncoding)) {
throw new TypeError(`Invalid character ${input[j]} in base64 string.`);
}
bits |= alphabetByEncoding[input[j]] << (limit - j) * bitsPerLetter;
bitLength += bitsPerLetter;
} else {
bits >>= bitsPerLetter;
}
}
const chunkOffset = i / 4 * 3;
Expand All @@ -63,9 +68,8 @@ var fromBase64 = (input) => {
}
return new Uint8Array(out);
};

// node_modules/.pnpm/@[email protected]/node_modules/@smithy/util-base64/dist-es/toBase64.browser.js
function toBase64(input) {
var base64urlEncode = (_input) => {
const input = typeof _input === "string" ? stringToBuffer(_input) : _input;
let str = "";
for (let i = 0; i < input.length; i += 3) {
let bits = 0;
Expand All @@ -80,22 +84,11 @@ function toBase64(input) {
const offset = (bitClusterCount - k) * bitsPerLetter;
str += alphabetByValue[(bits & maxLetterValue << offset) >> offset];
}
str += "==".slice(0, 4 - bitClusterCount);
}
return str;
}
};

// src/index.ts
var stringToBuffer = (value) => {
return new TextEncoder().encode(value);
};
var bufferToString = (value) => {
return new TextDecoder().decode(value);
};
var base64urlEncode = (value) => toBase64(typeof value === "string" ? stringToBuffer(value) : value).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
var base64urlDecode = (value) => fromBase64(
value.replace(/-/g, "+").replace(/_/g, "/") + Array((4 - value.length % 4) % 4 + 1).join("=")
);
var defaults = {
encryption: { saltBits: 256, algorithm: "aes-256-cbc", iterations: 1, minPasswordlength: 32 },
integrity: { saltBits: 256, algorithm: "sha256", iterations: 1, minPasswordlength: 32 },
Expand All @@ -114,15 +107,15 @@ var algorithms = {
sha256: { keyBits: 256, name: "SHA-256" }
};
var macFormatVersion = "2";
var macPrefix = `Fe26.${macFormatVersion}`;
var macPrefix = "Fe26.2";
var randomBytes = (_crypto, size) => {
const bytes = new Uint8Array(size);
_crypto.getRandomValues(bytes);
return bytes;
};
var randomBits = (_crypto, bits) => {
if (bits < 1)
throw Error("Invalid random bits count");
throw new Error("Invalid random bits count");
const bytes = Math.ceil(bits / 8);
return randomBytes(_crypto, bytes);
};
Expand Down Expand Up @@ -226,20 +219,20 @@ var normalizePassword = (password) => {
};
var seal = async (_crypto, object, password, options) => {
if (!password)
throw Error("Empty password");
throw new Error("Empty password");
const opts = clone(options);
const now = Date.now() + (opts.localtimeOffsetMsec || 0);
const objectString = JSON.stringify(object);
const pass = normalizePassword(password);
const { id = "" } = pass;
const { id = "", encryption, integrity } = pass;
if (id && !/^\w+$/.test(id))
throw new Error("Invalid password id");
const { encrypted, key } = await encrypt(_crypto, pass.encryption, opts.encryption, objectString);
const { encrypted, key } = await encrypt(_crypto, encryption, opts.encryption, objectString);
const encryptedB64 = base64urlEncode(new Uint8Array(encrypted));
const iv = base64urlEncode(key.iv);
const expiration = opts.ttl ? now + opts.ttl : "";
const macBaseString = `${macPrefix}*${id}*${key.salt}*${iv}*${encryptedB64}*${expiration}`;
const mac = await hmacWithPassword(_crypto, pass.integrity, opts.integrity, macBaseString);
const mac = await hmacWithPassword(_crypto, integrity, opts.integrity, macBaseString);
const sealed = `${macBaseString}*${mac.salt}*${mac.digest}`;
return sealed;
};
Expand All @@ -253,7 +246,7 @@ var fixedTimeComparison = (a, b) => {
};
var unseal = async (_crypto, sealed, password, options) => {
if (!password)
throw Error("Empty password");
throw new Error("Empty password");
const opts = clone(options);
const now = Date.now() + (opts.localtimeOffsetMsec || 0);
const parts = sealed.split("*");
Expand All @@ -271,22 +264,21 @@ var unseal = async (_crypto, sealed, password, options) => {
if (macPrefix !== prefix)
throw new Error("Wrong mac prefix");
if (expiration) {
if (!/^\d+$/.exec(expiration))
if (!/^\d+$/.test(expiration))
throw new Error("Invalid expiration");
const exp = parseInt(expiration, 10);
const exp = Number.parseInt(expiration, 10);
if (exp <= now - opts.timestampSkewSec * 1e3)
throw new Error("Expired seal");
}
if (typeof password === "undefined" || typeof password === "string" && password.length === 0)
throw new Error("Empty password");
let pass = "";
passwordId = passwordId || "default";
if (typeof password === "string" || password instanceof Uint8Array)
pass = password;
else if (!(passwordId in password))
throw new Error(`Cannot find password: ${passwordId}`);
else
else if (passwordId in password) {
pass = password[passwordId];
} else {
throw new Error(`Cannot find password: ${passwordId}`);
}
pass = normalizePassword(pass);
const macOptions = opts.integrity;
macOptions.salt = hmacSalt;
Expand Down

0 comments on commit 34cb0c7

Please sign in to comment.