@lakutata/security 3.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +143 -0
- package/THIRD_PARTY_NOTICES.md +36 -0
- package/dist/cjs/exceptions/DecryptionFailedException.d.ts +13 -0
- package/dist/cjs/exceptions/DecryptionFailedException.js +20 -0
- package/dist/cjs/exceptions/InvalidSignatureException.d.ts +13 -0
- package/dist/cjs/exceptions/InvalidSignatureException.js +20 -0
- package/dist/cjs/exceptions/PasswordTooLongException.d.ts +12 -0
- package/dist/cjs/exceptions/PasswordTooLongException.js +19 -0
- package/dist/cjs/exports/Security.d.ts +19 -0
- package/dist/cjs/exports/Security.js +38 -0
- package/dist/cjs/functions/Encryption.d.ts +95 -0
- package/dist/cjs/functions/Encryption.js +208 -0
- package/dist/cjs/functions/GenerateSecret.d.ts +20 -0
- package/dist/cjs/functions/GenerateSecret.js +26 -0
- package/dist/cjs/functions/HMAC.d.ts +50 -0
- package/dist/cjs/functions/HMAC.js +58 -0
- package/dist/cjs/functions/TimingSafeEqual.d.ts +18 -0
- package/dist/cjs/functions/TimingSafeEqual.js +30 -0
- package/dist/cjs/lib/AssertKey.d.ts +5 -0
- package/dist/cjs/lib/AssertKey.js +11 -0
- package/dist/cjs/lib/bcrypt/constants.d.ts +13 -0
- package/dist/cjs/lib/bcrypt/constants.js +213 -0
- package/dist/cjs/lib/bcrypt/index.d.ts +84 -0
- package/dist/cjs/lib/bcrypt/index.js +658 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/providers/Crypt.d.ts +71 -0
- package/dist/cjs/providers/Crypt.js +88 -0
- package/dist/cjs/providers/Mac.d.ts +86 -0
- package/dist/cjs/providers/Mac.js +114 -0
- package/dist/cjs/providers/PasswordHash.d.ts +78 -0
- package/dist/cjs/providers/PasswordHash.js +146 -0
- package/dist/esm/exceptions/DecryptionFailedException.js +16 -0
- package/dist/esm/exceptions/InvalidSignatureException.js +16 -0
- package/dist/esm/exceptions/PasswordTooLongException.js +15 -0
- package/dist/esm/exports/Security.js +19 -0
- package/dist/esm/functions/Encryption.js +202 -0
- package/dist/esm/functions/GenerateSecret.js +23 -0
- package/dist/esm/functions/HMAC.js +54 -0
- package/dist/esm/functions/TimingSafeEqual.js +27 -0
- package/dist/esm/lib/AssertKey.js +8 -0
- package/dist/esm/lib/bcrypt/constants.js +202 -0
- package/dist/esm/lib/bcrypt/index.js +645 -0
- package/dist/esm/providers/Crypt.js +84 -0
- package/dist/esm/providers/Mac.js +110 -0
- package/dist/esm/providers/PasswordHash.js +109 -0
- package/dist/types/exceptions/DecryptionFailedException.d.ts +13 -0
- package/dist/types/exceptions/InvalidSignatureException.d.ts +13 -0
- package/dist/types/exceptions/PasswordTooLongException.d.ts +12 -0
- package/dist/types/exports/Security.d.ts +19 -0
- package/dist/types/functions/Encryption.d.ts +95 -0
- package/dist/types/functions/GenerateSecret.d.ts +20 -0
- package/dist/types/functions/HMAC.d.ts +50 -0
- package/dist/types/functions/TimingSafeEqual.d.ts +18 -0
- package/dist/types/lib/AssertKey.d.ts +5 -0
- package/dist/types/lib/bcrypt/constants.d.ts +13 -0
- package/dist/types/lib/bcrypt/index.d.ts +84 -0
- package/dist/types/providers/Crypt.d.ts +71 -0
- package/dist/types/providers/Mac.d.ts +86 -0
- package/dist/types/providers/PasswordHash.d.ts +78 -0
- package/package.json +45 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A progress callback: the ratio of the rounds done (0 to 1), at most once per 100 ms
|
|
3
|
+
*/
|
|
4
|
+
export type ProgressCallback = (progress: number) => void;
|
|
5
|
+
/**
|
|
6
|
+
* Bytes: an array of byte values, or a typed array
|
|
7
|
+
*/
|
|
8
|
+
type Bytes = ArrayLike<number>;
|
|
9
|
+
/**
|
|
10
|
+
* Synchronously generates a salt.
|
|
11
|
+
* @param rounds Number of rounds to use, defaults to 10 if omitted (from 4 to 31)
|
|
12
|
+
* @returns Resulting salt
|
|
13
|
+
*/
|
|
14
|
+
export declare function genSaltSync(rounds?: number): string;
|
|
15
|
+
/**
|
|
16
|
+
* Asynchronously generates a salt.
|
|
17
|
+
* @param rounds Number of rounds to use, defaults to 10 if omitted
|
|
18
|
+
* @returns Resulting salt
|
|
19
|
+
*/
|
|
20
|
+
export declare function genSalt(rounds?: number): Promise<string>;
|
|
21
|
+
/**
|
|
22
|
+
* Synchronously generates a hash for the given password.
|
|
23
|
+
* @param password Password to hash
|
|
24
|
+
* @param salt Salt length to generate or salt to use, default to 10
|
|
25
|
+
* @returns Resulting hash
|
|
26
|
+
*/
|
|
27
|
+
export declare function hashSync(password: string, salt?: number | string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Asynchronously generates a hash for the given password.
|
|
30
|
+
* @param password Password to hash
|
|
31
|
+
* @param salt Salt length to generate or salt to use
|
|
32
|
+
* @param progressCallback Callback successively called with the percentage of rounds completed (0.0 - 1.0), maximally
|
|
33
|
+
* once per `MAX_EXECUTION_TIME = 100` ms.
|
|
34
|
+
* @returns Resulting hash
|
|
35
|
+
*/
|
|
36
|
+
export declare function hash(password: string, salt: number | string, progressCallback?: ProgressCallback): Promise<string>;
|
|
37
|
+
/**
|
|
38
|
+
* Synchronously tests a password against a hash.
|
|
39
|
+
* @param password Password to compare
|
|
40
|
+
* @param hash Hash to test against
|
|
41
|
+
* @returns true if matching, otherwise false
|
|
42
|
+
*/
|
|
43
|
+
export declare function compareSync(password: string, hash: string): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Asynchronously tests a password against a hash.
|
|
46
|
+
* @param password Password to compare
|
|
47
|
+
* @param hashValue Hash to test against
|
|
48
|
+
* @param progressCallback Callback successively called with the percentage of rounds completed (0.0 - 1.0), maximally
|
|
49
|
+
* once per `MAX_EXECUTION_TIME = 100` ms.
|
|
50
|
+
* @returns true if matching, otherwise false
|
|
51
|
+
*/
|
|
52
|
+
export declare function compare(password: string, hashValue: string, progressCallback?: ProgressCallback): Promise<boolean>;
|
|
53
|
+
/**
|
|
54
|
+
* Gets the number of rounds used to encrypt the specified hash.
|
|
55
|
+
* @param hash Hash to extract the used number of rounds from
|
|
56
|
+
* @returns Number of rounds used
|
|
57
|
+
*/
|
|
58
|
+
export declare function getRounds(hash: string): number;
|
|
59
|
+
/**
|
|
60
|
+
* Gets the salt portion from a hash. Does not validate the hash.
|
|
61
|
+
* @param hash Hash to extract the salt from
|
|
62
|
+
* @returns Extracted salt part
|
|
63
|
+
*/
|
|
64
|
+
export declare function getSalt(hash: string): string;
|
|
65
|
+
/**
|
|
66
|
+
* Tests if a password will be truncated when hashed, that is its length is greater than 72 bytes when converted to
|
|
67
|
+
* UTF-8.
|
|
68
|
+
* @param password The password to test
|
|
69
|
+
* @returns `true` if truncated, otherwise `false`
|
|
70
|
+
*/
|
|
71
|
+
export declare function truncates(password: string): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Encodes a byte array to base64 with up to len bytes of input, using the custom bcrypt alphabet.
|
|
74
|
+
* @param bytes Byte array
|
|
75
|
+
* @param length Maximum input length
|
|
76
|
+
*/
|
|
77
|
+
export declare function encodeBase64(bytes: Bytes, length: number): string;
|
|
78
|
+
/**
|
|
79
|
+
* Decodes a base64 encoded string to up to len bytes of output, using the custom bcrypt alphabet.
|
|
80
|
+
* @param string String to decode
|
|
81
|
+
* @param length Maximum output length
|
|
82
|
+
*/
|
|
83
|
+
export declare function decodeBase64(string: string, length: number): number[];
|
|
84
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Provider } from '@lakutata/core';
|
|
2
|
+
/**
|
|
3
|
+
* The encryption provider: encrypts data with AES-256-GCM (authenticated encryption), with key rotation (the first key
|
|
4
|
+
* encrypts, every key decrypts). Use it to keep secrets confidential at rest or through a client: tokens stored in a
|
|
5
|
+
* database, data put in a cookie. The encrypted data is also tamper-proof: changing a byte makes it fail to decrypt.
|
|
6
|
+
* It is not a password hash: a value that only needs to be checked, never read back, belongs to
|
|
7
|
+
* {@link PasswordHash}.
|
|
8
|
+
*
|
|
9
|
+
* Declare it in the `providers` of the application (`{class: Crypt, keys: [...]}`) and get it with `@Inject` or
|
|
10
|
+
* `getObject()`; it is scoped, like every provider. Each encryption derives a fresh key from the configured key and a
|
|
11
|
+
* random salt (HKDF-sha256) and uses a random IV, so encrypting the same data twice gives different outputs. The output
|
|
12
|
+
* is a base64url string (no padding) of 1 version byte, a 16-byte salt, a 12-byte IV, a 16-byte authentication tag and
|
|
13
|
+
* the ciphertext (45 bytes more than the data, before base64url), the same format as {@link encryptByKey}.
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import {Application} from 'lakutata'
|
|
17
|
+
* import {Crypt, DecryptionFailedException, generateSecret} from 'lakutata/security'
|
|
18
|
+
*
|
|
19
|
+
* Application
|
|
20
|
+
* .run(() => ({
|
|
21
|
+
* id: 'vault.app',
|
|
22
|
+
* name: 'Vault',
|
|
23
|
+
* providers: {
|
|
24
|
+
* //The keys come from the environment: the new key first, the previous one kept to decrypt
|
|
25
|
+
* crypt: {class: Crypt, keys: [process.env.CRYPT_KEY ?? generateSecret(), process.env.CRYPT_PREVIOUS_KEY ?? generateSecret()]}
|
|
26
|
+
* }
|
|
27
|
+
* }))
|
|
28
|
+
* .onLaunched(async (app: Application): Promise<void> => {
|
|
29
|
+
* const crypt: Crypt = await app.getObject('crypt')
|
|
30
|
+
* const encrypted: string = crypt.encrypt('api-token-123')
|
|
31
|
+
* try {
|
|
32
|
+
* console.log(crypt.decryptString(encrypted))
|
|
33
|
+
* console.log(crypt.decrypt(encrypted).length)
|
|
34
|
+
* } catch (error) {
|
|
35
|
+
* if (error instanceof DecryptionFailedException) console.log('Changed, or encrypted with an unknown key')
|
|
36
|
+
* }
|
|
37
|
+
* })
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare class Crypt extends Provider {
|
|
41
|
+
/**
|
|
42
|
+
* The secret keys, at least one, each a string of at least 16 characters used as its UTF-8 bytes (it is not decoded
|
|
43
|
+
* from base64). The first key encrypts, every key decrypts: to rotate, put the new key first and keep the previous
|
|
44
|
+
* ones while data encrypted with them is still read. Use random keys, such as the output of {@link generateSecret}: the
|
|
45
|
+
* key derivation is fast, so a low-entropy key (a password) is open to brute force; to encrypt with a password, use
|
|
46
|
+
* {@link encryptByPassword}. Required.
|
|
47
|
+
*/
|
|
48
|
+
readonly keys: string[];
|
|
49
|
+
/**
|
|
50
|
+
* Encrypt data with the first key.
|
|
51
|
+
* @param data The data to encrypt: a string (encrypted as its UTF-8 bytes) or bytes.
|
|
52
|
+
* @returns The encrypted data, base64url encoded without padding.
|
|
53
|
+
*/
|
|
54
|
+
encrypt(data: string | Uint8Array): string;
|
|
55
|
+
/**
|
|
56
|
+
* Decrypt data encrypted by {@link Crypt.encrypt} (or {@link encryptByKey}) with any of the keys, tried in order.
|
|
57
|
+
* @param encrypted The encrypted data, base64url as {@link Crypt.encrypt} returns it.
|
|
58
|
+
* @returns The decrypted bytes.
|
|
59
|
+
* @throws {DecryptionFailedException} When no key decrypts the data, or it was changed, truncated or not produced by
|
|
60
|
+
* {@link Crypt.encrypt}.
|
|
61
|
+
*/
|
|
62
|
+
decrypt(encrypted: string): Buffer;
|
|
63
|
+
/**
|
|
64
|
+
* Decrypt data encrypted by {@link Crypt.encrypt} with any of the keys, and decode it as a UTF-8 string.
|
|
65
|
+
* @param encrypted The encrypted data, base64url as {@link Crypt.encrypt} returns it.
|
|
66
|
+
* @returns The decrypted string.
|
|
67
|
+
* @throws {DecryptionFailedException} When no key decrypts the data, or it was changed, truncated or not produced by
|
|
68
|
+
* {@link Crypt.encrypt}.
|
|
69
|
+
*/
|
|
70
|
+
decryptString(encrypted: string): string;
|
|
71
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Provider } from '@lakutata/core';
|
|
2
|
+
import { type HMACAlgorithm } from '../functions/HMAC.js';
|
|
3
|
+
/**
|
|
4
|
+
* The message signing provider: signs data with an HMAC (SHA-256 by default) and verifies the signatures, with key
|
|
5
|
+
* rotation (the first key signs, every key verifies). Use it to detect tampering with the data that goes through a
|
|
6
|
+
* client: signed cookies, links, webhook payloads, tokens. A signature proves the data was signed by a holder of a key;
|
|
7
|
+
* it does not hide the data (use {@link Crypt}) and is not a password hash (use {@link PasswordHash}).
|
|
8
|
+
*
|
|
9
|
+
* Declare it in the `providers` of the application (`{class: Mac, keys: [...]}`) and get it with `@Inject` or
|
|
10
|
+
* `getObject()`; it is scoped, like every provider. The signatures are base64url strings without padding (43
|
|
11
|
+
* characters with SHA-256, 64 with SHA-384, 86 with SHA-512) and never expire by themselves: put a time in the signed
|
|
12
|
+
* data to limit their validity. Verification takes a constant time and tries every key, so it does not reveal which
|
|
13
|
+
* key failed.
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import {Application} from 'lakutata'
|
|
17
|
+
* import {generateSecret, InvalidSignatureException, Mac} from 'lakutata/security'
|
|
18
|
+
*
|
|
19
|
+
* Application
|
|
20
|
+
* .run(() => ({
|
|
21
|
+
* id: 'links.app',
|
|
22
|
+
* name: 'Links',
|
|
23
|
+
* providers: {
|
|
24
|
+
* //The keys come from the environment: the new key first, the previous one kept to verify
|
|
25
|
+
* mac: {class: Mac, keys: [process.env.MAC_KEY ?? generateSecret(), process.env.MAC_PREVIOUS_KEY ?? generateSecret()]}
|
|
26
|
+
* }
|
|
27
|
+
* }))
|
|
28
|
+
* .onLaunched(async (app: Application): Promise<void> => {
|
|
29
|
+
* const mac: Mac = await app.getObject('mac')
|
|
30
|
+
* //'user:42.<signature>'
|
|
31
|
+
* const token: string = mac.signMessage('user:42')
|
|
32
|
+
* try {
|
|
33
|
+
* console.log(mac.unsignMessage(token))
|
|
34
|
+
* } catch (error) {
|
|
35
|
+
* if (error instanceof InvalidSignatureException) console.log('Tampered token')
|
|
36
|
+
* }
|
|
37
|
+
* //A detached signature, sent alongside the body
|
|
38
|
+
* const signature: string = mac.sign('{"order":42}')
|
|
39
|
+
* console.log(mac.verify('{"order":42}', signature))
|
|
40
|
+
* })
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare class Mac extends Provider {
|
|
44
|
+
/**
|
|
45
|
+
* The secret keys, at least one, each a string of at least 16 characters used as its UTF-8 bytes (it is not decoded
|
|
46
|
+
* from base64). The first key signs, every key verifies: to rotate, put the new key first and keep the previous ones
|
|
47
|
+
* while their signatures are still presented. Use random keys, such as the output of {@link generateSecret}, not
|
|
48
|
+
* passwords. Required.
|
|
49
|
+
*/
|
|
50
|
+
readonly keys: string[];
|
|
51
|
+
/**
|
|
52
|
+
* The hash function of the HMAC: `'sha256'`, `'sha384'` or `'sha512'`. Changing it invalidates every signature made
|
|
53
|
+
* before.
|
|
54
|
+
* @default 'sha256'
|
|
55
|
+
*/
|
|
56
|
+
readonly algorithm: HMACAlgorithm;
|
|
57
|
+
/**
|
|
58
|
+
* Sign data with the first key.
|
|
59
|
+
* @param data The data to sign: a string (signed as its UTF-8 bytes) or bytes.
|
|
60
|
+
* @returns The HMAC of the data, base64url encoded without padding.
|
|
61
|
+
*/
|
|
62
|
+
sign(data: string | Uint8Array): string;
|
|
63
|
+
/**
|
|
64
|
+
* Check a detached signature made by {@link Mac.sign} with any of the keys, in a time that does not depend on the
|
|
65
|
+
* signature or on the key that matches.
|
|
66
|
+
* @param data The signed data, exactly as it was signed (same string or same bytes).
|
|
67
|
+
* @param signature The signature to check, base64url as {@link Mac.sign} returns it.
|
|
68
|
+
* @returns `true` when one of the keys signed the data; `false` otherwise (never throws on a malformed signature).
|
|
69
|
+
*/
|
|
70
|
+
verify(data: string | Uint8Array, signature: string): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Sign a message and append the signature: `<message>.<signature>`. The message is not encrypted: anyone can read
|
|
73
|
+
* it, only a key holder can change it.
|
|
74
|
+
* @param message The message to sign; it may contain dots (the signature follows the last one).
|
|
75
|
+
* @returns The signed message, to read back with {@link Mac.unsignMessage}.
|
|
76
|
+
*/
|
|
77
|
+
signMessage(message: string): string;
|
|
78
|
+
/**
|
|
79
|
+
* Check a message signed by {@link Mac.signMessage} with any of the keys, and return it without its signature.
|
|
80
|
+
* @param signedMessage The signed message, `<message>.<signature>`.
|
|
81
|
+
* @returns The original message.
|
|
82
|
+
* @throws {InvalidSignatureException} When the signed message has no dot, or its signature is not valid (the message
|
|
83
|
+
* or the signature was changed, or none of the keys signed it).
|
|
84
|
+
*/
|
|
85
|
+
unsignMessage(signedMessage: string): string;
|
|
86
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Provider } from '@lakutata/core';
|
|
2
|
+
/**
|
|
3
|
+
* The password hashing provider: hashes passwords with bcrypt (a new random 16-byte salt per hash, the `$2b$` variant)
|
|
4
|
+
* and checks a password against a stored hash. Use it for user passwords: bcrypt is deliberately slow, so a stolen hash
|
|
5
|
+
* is costly to brute-force. Do not use it to hash tokens or keys (use {@link hmac}) or to encrypt (use {@link Crypt}).
|
|
6
|
+
*
|
|
7
|
+
* Declare it in the `providers` of the application (`{class: PasswordHash, saltRounds: 12}`) and get it with `@Inject`
|
|
8
|
+
* or `getObject()`; it is scoped, like every provider. A hash is a 60-character string (`$2b$10$` then the salt and the
|
|
9
|
+
* digest) holding its own salt and cost, so it is stored as is, in one column, and {@link PasswordHash.validate} needs
|
|
10
|
+
* nothing else; hashes of another cost (or the `$2a$`/`$2y$` variants of other bcrypt libraries) still validate.
|
|
11
|
+
*
|
|
12
|
+
* bcrypt only reads the first 72 bytes of a password (UTF-8): {@link PasswordHash.hash} rejects a longer password with
|
|
13
|
+
* {@link PasswordTooLongException} rather than hashing its prefix, and {@link PasswordHash.validate} gives `false` for
|
|
14
|
+
* it. bcrypt runs in JavaScript on the main thread, in
|
|
15
|
+
* slices of at most 100 ms between which the other requests proceed; a hash or a validation costs about 60 ms of CPU
|
|
16
|
+
* at cost 10 on a current CPU, doubling with each extra round.
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import {Application} from 'lakutata'
|
|
20
|
+
* import {PasswordHash, PasswordTooLongException} from 'lakutata/provider/passwordHash'
|
|
21
|
+
*
|
|
22
|
+
* Application
|
|
23
|
+
* .run(() => ({
|
|
24
|
+
* id: 'auth.app',
|
|
25
|
+
* name: 'Auth',
|
|
26
|
+
* providers: {
|
|
27
|
+
* //Each extra round doubles the cost of a hash
|
|
28
|
+
* passwordHash: {class: PasswordHash, saltRounds: 12}
|
|
29
|
+
* }
|
|
30
|
+
* }))
|
|
31
|
+
* .onLaunched(async (app: Application): Promise<void> => {
|
|
32
|
+
* const passwordHash: PasswordHash = await app.getObject('passwordHash')
|
|
33
|
+
* try {
|
|
34
|
+
* //Stored as is: the hash holds its salt and its cost
|
|
35
|
+
* const hash: string = await passwordHash.hash('correct horse battery staple')
|
|
36
|
+
* console.log(await passwordHash.validate('correct horse battery staple', hash))
|
|
37
|
+
* console.log(await passwordHash.validate('wrong password', hash))
|
|
38
|
+
* } catch (error) {
|
|
39
|
+
* if (error instanceof PasswordTooLongException) console.log('Choose a password of 72 bytes at most')
|
|
40
|
+
* }
|
|
41
|
+
* })
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare class PasswordHash extends Provider {
|
|
45
|
+
/**
|
|
46
|
+
* The bcrypt cost of the new hashes, as a base-2 logarithm: a hash runs 2^saltRounds iterations, so each extra round
|
|
47
|
+
* doubles the time of {@link PasswordHash.hash} (and of {@link PasswordHash.validate} on the hashes it makes). An
|
|
48
|
+
* integer from 4 (bcrypt's minimum) to 31. Raise it as far as the login latency allows (12 is a common choice); the
|
|
49
|
+
* existing hashes keep the cost they were made with.
|
|
50
|
+
* @default 10
|
|
51
|
+
*/
|
|
52
|
+
readonly saltRounds: number;
|
|
53
|
+
/**
|
|
54
|
+
* A new random bcrypt salt with the configured cost (`$2b$10$` and 22 characters), different at each read.
|
|
55
|
+
* @deprecated Not needed: {@link PasswordHash.hash} generates a salt for each hash, and the hash holds it for
|
|
56
|
+
* {@link PasswordHash.validate}.
|
|
57
|
+
*/
|
|
58
|
+
get salt(): string;
|
|
59
|
+
/**
|
|
60
|
+
* Hash a password with bcrypt, a new random salt and the configured {@link PasswordHash.saltRounds}. Two hashes of
|
|
61
|
+
* the same password differ: compare them with {@link PasswordHash.validate}, never with `===`.
|
|
62
|
+
* @param password The password, as typed by the user: at most 72 bytes once encoded in UTF-8 (72 ASCII
|
|
63
|
+
* characters, fewer with accented letters or emoji). An empty password is hashed too: reject it beforehand if needed.
|
|
64
|
+
* @returns The 60-character bcrypt hash (`$2b$<cost>$<salt><digest>`, bcrypt's own base64 alphabet), to store as is.
|
|
65
|
+
* @throws {PasswordTooLongException} When the password is longer than 72 bytes in UTF-8.
|
|
66
|
+
*/
|
|
67
|
+
hash(password: string): Promise<string>;
|
|
68
|
+
/**
|
|
69
|
+
* Check a password against a hash made by {@link PasswordHash.hash} (or by another bcrypt library: `$2a$`, `$2b$`,
|
|
70
|
+
* `$2y$`), with the salt and the cost the hash holds; the final comparison takes a constant time.
|
|
71
|
+
* @param password The password to check. A password longer than 72 bytes in UTF-8 gives `false`:
|
|
72
|
+
* {@link PasswordHash.hash} refuses it, so no hash can be its own.
|
|
73
|
+
* @param hash The stored bcrypt hash. A value which is not a bcrypt hash (not 60 characters long, a wrong prefix,
|
|
74
|
+
* cost or alphabet) gives `false`.
|
|
75
|
+
* @returns `true` when the password matches the hash.
|
|
76
|
+
*/
|
|
77
|
+
validate(password: string, hash: string): Promise<boolean>;
|
|
78
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lakutata/security",
|
|
3
|
+
"version": "3.0.0-beta.0",
|
|
4
|
+
"description": "Security providers and functions of the lakutata framework (password hashing, signatures, encryption).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/cjs/exports/Security.js",
|
|
7
|
+
"types": "./dist/cjs/exports/Security.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/types/exports/Security.d.ts",
|
|
12
|
+
"default": "./dist/esm/exports/Security.js"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/cjs/exports/Security.d.ts",
|
|
16
|
+
"default": "./dist/cjs/exports/Security.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"clean": "node -e \"require('node:fs').rmSync('dist', {recursive: true, force: true})\"",
|
|
23
|
+
"build:cjs": "tsc -p tsconfig.cjs.json",
|
|
24
|
+
"build:esm": "tsc -p tsconfig.esm.json",
|
|
25
|
+
"build:cjs-pkg": "node ../../scripts/package-build/cjs-package.mjs",
|
|
26
|
+
"build:js": "npm run build:cjs && npm run build:esm && npm run build:cjs-pkg",
|
|
27
|
+
"rebuild": "npm run clean && npm run build:js",
|
|
28
|
+
"compile": "npm run rebuild",
|
|
29
|
+
"test:unit": "npm run compile && node --test \"dist/esm/tests/*.spec.js\""
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"@lakutata/core": "3.0.0-beta.0"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=22"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"dist",
|
|
39
|
+
"!dist/*/tests",
|
|
40
|
+
"*.md",
|
|
41
|
+
"LICENSE",
|
|
42
|
+
"package.json",
|
|
43
|
+
"THIRD_PARTY_NOTICES.md"
|
|
44
|
+
]
|
|
45
|
+
}
|