diff --git a/README.md b/README.md index 598cd3b..6eb13db 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,19 @@ # Argon2 for Deno [Argon2](https://github.com/P-H-C/phc-winner-argon2) encryption library for [Deno](https://deno.land). + It uses [rust-argon2](https://github.com/sru-systems/rust-argon2) under the hood. +## API + +- `hash(password: string, options?: HashOptions): Promise` + +- `verify(hash: string, password: string): Promise` + +### Error handling + +In case of error, all methods of this library will throw an [`Argon2Error`](src/error.ts) type. + ## Usage ```ts import { assert } from "https://deno.land/std/testing/asserts.ts"; @@ -13,15 +24,27 @@ let hash = await hash("test"); assert(await verify(hash, "test")); ``` -## API +### CLI -- `hash(password: string, options: HashOptions): Promise` +It is possible to install deno-argon2 as a CLI tool insatiable via `deno install`. -- `verify(hash: string, password: string): Promise` +
-### Error handling + Installation snippet -In case of error, all methods of this library will throw an [`Argon2Error`](src/error.ts) type. + ```sh + deno install \ + --allow-env \ + --allow-run \ + --allow-read \ + --allow-write \ + --allow-plugin \ + --allow-net \ + argon2 https://deno.land/x/argon2/cli/mod.ts + ``` +
+ +After install run `--help` to inspect all possible commands. ## Permissions @@ -33,6 +56,7 @@ This library automatically download the static library and initialize Deno plugi deno \ --allow-read .deno_plugins \ --allow-write .deno_plugins \ + --allow-net --allow-plugin \ src/mod.ts ``` diff --git a/cli/commands/hash.ts b/cli/commands/hash.ts new file mode 100755 index 0000000..665e536 --- /dev/null +++ b/cli/commands/hash.ts @@ -0,0 +1,70 @@ +import { Command, encode, argon2 } from "../deps.ts"; +import { readStdin } from "../util.ts"; + +export let hash = new Command() + .version(argon2.version()) + .description("Hash a new password or verify an already existing one.") + .option("-s, --salt ", "") + .option("-S, --secret ", "") + .option("-m, --memory-cost ", "") + .option("-t, --time-cost ", "") + .option("-l, --lanes ", "") + .option("-T, --thread-mode ", "") + .option("-v, --variant ", "") + .option("-d, --data ", "") + .type("thread-mode", (option, _, value) => { + switch (value) { + case "sequential": { + return argon2.ThreadMode.Sequential; + } + case "parallel": { + return argon2.ThreadMode.Parallel; + } + case undefined: {} + default: { + throw new Error( + `Option --${option.name} must be either "sequential" or "parallel": ${value}`, + ); + } + } + }) + .type("variant", (option, _, value) => { + switch (value) { + case argon2.Variant.Argon2i: + case argon2.Variant.Argon2d: + case argon2.Variant.Argon2id: { + return value; + } + case undefined: {} + default: { + throw new Error( + `Option --${option.name} must be either "${argon2.Variant.Argon2i}", "${argon2.Variant.Argon2d}" or "${argon2.Variant.Argon2id}": ${value}`, + ); + } + } + }) + .type("json", (option, _, value) => { + try { + if (value !== undefined) { + return JSON.parse(value); + } + } catch (_) { + throw new Error( + `Option --${option.name} must be a valid json object: ${value}`, + ); + } + }) + .action(async (options) => { + let password = await readStdin(); + + console.log(await argon2.hash(password, { + salt: options.salt ? encode(options.salt) : undefined, + secret: options.secret ? encode(options.secret) : undefined, + memoryCost: options.memoryCost ? options.memoryCost : undefined, + timeCost: options.timeCost ? options.timeCost : undefined, + lanes: options.lanes ? options.lanes : undefined, + threadMode: "threadMode" in options ? options.threadMode : undefined, + variant: options.variant ? options.variant : undefined, + data: options.data ? options.data : undefined, + })); + }); diff --git a/cli/commands/verify.ts b/cli/commands/verify.ts new file mode 100755 index 0000000..eb05d83 --- /dev/null +++ b/cli/commands/verify.ts @@ -0,0 +1,13 @@ +import { Command, argon2 } from "../deps.ts"; + +import { readStdin } from "../util.ts"; + +export let verify = new Command() + .version(argon2.version()) + .description("Hash a new password or verify an already existing one.") + .option("-H, --hash ", "", { required: true }) + .action(async (options) => { + let password = await readStdin(); + + console.log(await argon2.verify(options.hash, password)); + }); diff --git a/cli/deps.ts b/cli/deps.ts new file mode 100644 index 0000000..c7dab84 --- /dev/null +++ b/cli/deps.ts @@ -0,0 +1,5 @@ +export { Command } from "https://deno.land/x/cliffy/command.ts"; + +export { encode, decode } from "../src/deps.ts"; + +export * as argon2 from "../src/mod.ts"; diff --git a/cli/mod.ts b/cli/mod.ts new file mode 100644 index 0000000..6aac34c --- /dev/null +++ b/cli/mod.ts @@ -0,0 +1,11 @@ +import { Command, argon2 } from "./deps.ts"; + +import { hash } from "./commands/hash.ts"; +import { verify } from "./commands/verify.ts"; + +await new Command() + .version(argon2.version()) + .description("Hash a new password or verify an existing one.") + .command("hash", hash) + .command("verify", verify) + .parse(Deno.args); diff --git a/cli/util.ts b/cli/util.ts new file mode 100644 index 0000000..3b48b69 --- /dev/null +++ b/cli/util.ts @@ -0,0 +1,5 @@ +import { decode } from "./deps.ts"; + +export async function readStdin() { + return decode(await Deno.readAll(Deno.stdin)); +} diff --git a/src/common.ts b/src/common.ts index fc6e9a9..65f33cc 100644 --- a/src/common.ts +++ b/src/common.ts @@ -27,3 +27,7 @@ export interface HashOptions { threadMode: ThreadMode; lanes: number; } + +export function version() { + return "0.3.0"; +} diff --git a/src/deps.ts b/src/deps.ts new file mode 100644 index 0000000..f6e77dc --- /dev/null +++ b/src/deps.ts @@ -0,0 +1,6 @@ +export { encode, decode } from "https://deno.land/std/encoding/utf8.ts"; + +export { + prepare, + PreprareOptions, +} from "https://deno.land/x/plugin_prepare@v0.3.1/mod.ts"; diff --git a/src/internal.ts b/src/internal.ts index 370e39a..af6b2dc 100644 --- a/src/internal.ts +++ b/src/internal.ts @@ -1,9 +1,4 @@ -import { encode, decode } from "https://deno.land/std/encoding/utf8.ts"; - -import { - prepare, - PreprareOptions, -} from "https://deno.land/x/plugin_prepare@v0.3.1/mod.ts"; +import { encode, decode, prepare, PreprareOptions } from "./deps.ts"; import { MIN_SALT_SIZE, HashOptions } from "./common.ts"; import { Argon2ErrorType, Argon2Error } from "./error.ts"; @@ -58,6 +53,13 @@ export async function installPlugin( ) { let plugin = await preparing; + if (typeof password !== "string") { + throw new Argon2Error( + Argon2ErrorType.InvalidInput, + "Password argument must be a string.", + ); + } + let salt = options.salt ? options.salt : crypto.getRandomValues( diff --git a/src/mod.ts b/src/mod.ts index b8e8969..9e37815 100644 --- a/src/mod.ts +++ b/src/mod.ts @@ -1,15 +1,16 @@ -import { HashOptions } from "./common.ts"; +import { HashOptions, version } from "./common.ts"; import { installPlugin } from "./internal.ts"; export * from "./common.ts"; export * from "./error.ts"; -export let version = "0.2.0"; - -let plugin = await installPlugin(`https://github.com/fdionisi/deno-argon2/releases/download/v${version}`, { - printLog: false, - checkCache: true, -}); +let plugin = await installPlugin( + `https://github.com/fdionisi/deno-argon2/releases/download/v${version()}`, + { + printLog: false, + checkCache: true, + }, +); /** * Hash a string. diff --git a/tests/cli.ts b/tests/cli.ts new file mode 100755 index 0000000..fac55b1 --- /dev/null +++ b/tests/cli.ts @@ -0,0 +1,3 @@ +#!/usr/bin/env -S deno --allow-env --allow-run --allow-read --allow-write --allow-net --allow-plugin + +import "../cli/mod.ts"; diff --git a/tests/deno.test.ts b/tests/deno.test.ts index 818049c..daf7ab9 100644 --- a/tests/deno.test.ts +++ b/tests/deno.test.ts @@ -154,4 +154,4 @@ Deno.test({ }, }); -Deno.runTests() +Deno.runTests();