node-argon2 icon indicating copy to clipboard operation
node-argon2 copied to clipboard

Cannot convert literal false to boolean type when calling overloaded hash function with defined options

Open sesopenko opened this issue 3 years ago • 3 comments

Welcome to the issues section if it's your first time!

Steps to reproduce

  1. Install typescript 3 or later
  2. Enable strict type checking for typescript
  3. Enable strict null checks for typescript
  4. Attempt a hash with an options object: tsc
// This must be in a function body so await keyword below works.  Can't use await in a module body.
import {argon2id, hash, Options} from "argon2";
const secretBuffer = new Buffer("example-pepper", "ascii");
const options: Options = {
    hashLength: 32,
    saltLength: 16,
    type: argon2id,
    memoryCost: 1<<14,
    parallelism: 1,
    timeCost: 2,
    secret: secretBuffer,
}
const password = "password";
const hashString: string = await hash(password, options)

Expected behaviour

Should hash password

Actual behaviour

 error TS2769: No overload matches this call.
  Overload 1 of 2, '(plain: string | Buffer, options: Options & { raw: true; }): Promise<Buffer>', gave the following error.
    Argument of type 'Options' is not assignable to parameter of type 'Options & { raw: true; }'.
      Type 'Options' is not assignable to type '{ raw: true; }'.
        Types of property 'raw' are incompatible.
          Type 'boolean | undefined' is not assignable to type 'true'.
            Type 'undefined' is not assignable to type 'true'.
  Overload 2 of 2, '(plain: string | Buffer, options?: (Options & { raw?: false | undefined; }) | undefined): Promise<string>', gave the following error.
    Argument of type 'Options' is not assignable to parameter of type 'Options & { raw?: false | undefined; }'.
      Type 'Options' is not assignable to type '{ raw?: false | undefined; }'.
        Types of property 'raw' are incompatible.
          Type 'boolean | undefined' is not assignable to type 'false | undefined'.
            Type 'true' is not assignable to type 'false | undefined'.

66         const hash: string = await hash(password, options)

Environment

Operating system: Windows 10 Pro 21H1 build 19043.1052

Node version: v16.4.0

Compiler version: Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30038.1 for x64

sesopenko avatar Jul 04 '21 01:07 sesopenko

This may be a limitation of typescript and if so a workaround would be changing this:

export interface Options {
    hashLength?: number;
    timeCost?: number;
    memoryCost?: number;
    parallelism?: number;
    type?: 0 | 1 | 2;
    version?: number;
    salt?: Buffer;
    saltLength?: number;
    raw?: boolean;
    secret?: Buffer;
    associatedData?: Buffer;
}

To this:

export interface Options {
    hashLength?: number;
    timeCost?: number;
    memoryCost?: number;
    parallelism?: number;
    type?: 0 | 1 | 2;
    version?: number;
    salt?: Buffer;
    saltLength?: number;
    raw?: true|false;
    secret?: Buffer;
    associatedData?: Buffer;
}

I'm not sure if that'll work though, nor how backwards compatible is. I've worked around it with a // ts-ignore when calling hash.

sesopenko avatar Jul 04 '21 01:07 sesopenko

The problem is that the output type changes depending on the raw parameter, which makes it cumbersome to type.

However, we have automated testing for the Typescript interface and it tests all the parameter values, so it may be a misconfigured option on your side

ranisalt avatar Jul 04 '21 19:07 ranisalt

The issue is defining the variable using just the Options type from the library, because of the typing issue mentioned above. I did try coming up with some new type definitions but the closest I got was deleting & {raw?: false} from the hash function type that had options as an optional parameter:

export function hash(plain: Buffer | string, options: Options & {raw: true}): Promise<Buffer>;
export function hash(plain: Buffer | string, options?: Options): Promise<string>;

This at least lets the code above compile, however a caveat of this is that if you pass in a variable with type Options and not Options & {raw: true} it will need to be casted to a Buffer as TypeScript will then match the latter type regardless of what, if anything, raw is set to. That said - if it's an anonymous object (i.e., passed in directly, then the typing works correctly so it's a small price to pay I guess).


As a workaround for an individual project using the library, you can simply append either & { raw?: false } or & { raw: true } to the Options type from argon2 - or even have both objects to hand, as shown below:

import { argon2id, Options as Argon2HashingOptions } from "argon2";

export const myAppHashingOptions: Argon2HashingOptions & { raw?: false } = {
    type: argon2id,
    timeCost: 8,
    raw: false,
};

export const myAppRawHashingOptions: Argon2HashingOptions & { raw: true } = {
    ...clubGuardHashingOptions,
    // Will override 'raw: false' from the other object.
    raw: true
};

SamJakob avatar Nov 02 '21 02:11 SamJakob

This problem shouldn't happen anymore with the latest version, and the types have been improved.

ranisalt avatar Dec 09 '23 00:12 ranisalt