@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,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,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.Crypt = void 0;
|
|
13
|
+
const core_1 = require("@lakutata/core");
|
|
14
|
+
const di_1 = require("@lakutata/core/decorator/di");
|
|
15
|
+
const Encryption_js_1 = require("../functions/Encryption.js");
|
|
16
|
+
/**
|
|
17
|
+
* The encryption provider: encrypts data with AES-256-GCM (authenticated encryption), with key rotation (the first key
|
|
18
|
+
* encrypts, every key decrypts). Use it to keep secrets confidential at rest or through a client: tokens stored in a
|
|
19
|
+
* database, data put in a cookie. The encrypted data is also tamper-proof: changing a byte makes it fail to decrypt.
|
|
20
|
+
* It is not a password hash: a value that only needs to be checked, never read back, belongs to
|
|
21
|
+
* {@link PasswordHash}.
|
|
22
|
+
*
|
|
23
|
+
* Declare it in the `providers` of the application (`{class: Crypt, keys: [...]}`) and get it with `@Inject` or
|
|
24
|
+
* `getObject()`; it is scoped, like every provider. Each encryption derives a fresh key from the configured key and a
|
|
25
|
+
* random salt (HKDF-sha256) and uses a random IV, so encrypting the same data twice gives different outputs. The output
|
|
26
|
+
* is a base64url string (no padding) of 1 version byte, a 16-byte salt, a 12-byte IV, a 16-byte authentication tag and
|
|
27
|
+
* the ciphertext (45 bytes more than the data, before base64url), the same format as {@link encryptByKey}.
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* import {Application} from 'lakutata'
|
|
31
|
+
* import {Crypt, DecryptionFailedException, generateSecret} from 'lakutata/security'
|
|
32
|
+
*
|
|
33
|
+
* Application
|
|
34
|
+
* .run(() => ({
|
|
35
|
+
* id: 'vault.app',
|
|
36
|
+
* name: 'Vault',
|
|
37
|
+
* providers: {
|
|
38
|
+
* //The keys come from the environment: the new key first, the previous one kept to decrypt
|
|
39
|
+
* crypt: {class: Crypt, keys: [process.env.CRYPT_KEY ?? generateSecret(), process.env.CRYPT_PREVIOUS_KEY ?? generateSecret()]}
|
|
40
|
+
* }
|
|
41
|
+
* }))
|
|
42
|
+
* .onLaunched(async (app: Application): Promise<void> => {
|
|
43
|
+
* const crypt: Crypt = await app.getObject('crypt')
|
|
44
|
+
* const encrypted: string = crypt.encrypt('api-token-123')
|
|
45
|
+
* try {
|
|
46
|
+
* console.log(crypt.decryptString(encrypted))
|
|
47
|
+
* console.log(crypt.decrypt(encrypted).length)
|
|
48
|
+
* } catch (error) {
|
|
49
|
+
* if (error instanceof DecryptionFailedException) console.log('Changed, or encrypted with an unknown key')
|
|
50
|
+
* }
|
|
51
|
+
* })
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
class Crypt extends core_1.Provider {
|
|
55
|
+
/**
|
|
56
|
+
* Encrypt data with the first key.
|
|
57
|
+
* @param data The data to encrypt: a string (encrypted as its UTF-8 bytes) or bytes.
|
|
58
|
+
* @returns The encrypted data, base64url encoded without padding.
|
|
59
|
+
*/
|
|
60
|
+
encrypt(data) {
|
|
61
|
+
return (0, Encryption_js_1.encryptByKey)(data, this.keys[0]);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Decrypt data encrypted by {@link Crypt.encrypt} (or {@link encryptByKey}) with any of the keys, tried in order.
|
|
65
|
+
* @param encrypted The encrypted data, base64url as {@link Crypt.encrypt} returns it.
|
|
66
|
+
* @returns The decrypted bytes.
|
|
67
|
+
* @throws {DecryptionFailedException} When no key decrypts the data, or it was changed, truncated or not produced by
|
|
68
|
+
* {@link Crypt.encrypt}.
|
|
69
|
+
*/
|
|
70
|
+
decrypt(encrypted) {
|
|
71
|
+
return (0, Encryption_js_1.decryptByKey)(encrypted, this.keys);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Decrypt data encrypted by {@link Crypt.encrypt} with any of the keys, and decode it as a UTF-8 string.
|
|
75
|
+
* @param encrypted The encrypted data, base64url as {@link Crypt.encrypt} returns it.
|
|
76
|
+
* @returns The decrypted string.
|
|
77
|
+
* @throws {DecryptionFailedException} When no key decrypts the data, or it was changed, truncated or not produced by
|
|
78
|
+
* {@link Crypt.encrypt}.
|
|
79
|
+
*/
|
|
80
|
+
decryptString(encrypted) {
|
|
81
|
+
return this.decrypt(encrypted).toString('utf8');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
exports.Crypt = Crypt;
|
|
85
|
+
__decorate([
|
|
86
|
+
(0, di_1.Configurable)(core_1.DTO.Array().items(core_1.DTO.String().min(16)).min(1).required()),
|
|
87
|
+
__metadata("design:type", Array)
|
|
88
|
+
], Crypt.prototype, "keys", void 0);
|
|
@@ -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,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.Mac = void 0;
|
|
13
|
+
const core_1 = require("@lakutata/core");
|
|
14
|
+
const di_1 = require("@lakutata/core/decorator/di");
|
|
15
|
+
const HMAC_js_1 = require("../functions/HMAC.js");
|
|
16
|
+
const InvalidSignatureException_js_1 = require("../exceptions/InvalidSignatureException.js");
|
|
17
|
+
/**
|
|
18
|
+
* The message signing provider: signs data with an HMAC (SHA-256 by default) and verifies the signatures, with key
|
|
19
|
+
* rotation (the first key signs, every key verifies). Use it to detect tampering with the data that goes through a
|
|
20
|
+
* client: signed cookies, links, webhook payloads, tokens. A signature proves the data was signed by a holder of a key;
|
|
21
|
+
* it does not hide the data (use {@link Crypt}) and is not a password hash (use {@link PasswordHash}).
|
|
22
|
+
*
|
|
23
|
+
* Declare it in the `providers` of the application (`{class: Mac, keys: [...]}`) and get it with `@Inject` or
|
|
24
|
+
* `getObject()`; it is scoped, like every provider. The signatures are base64url strings without padding (43
|
|
25
|
+
* characters with SHA-256, 64 with SHA-384, 86 with SHA-512) and never expire by themselves: put a time in the signed
|
|
26
|
+
* data to limit their validity. Verification takes a constant time and tries every key, so it does not reveal which
|
|
27
|
+
* key failed.
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* import {Application} from 'lakutata'
|
|
31
|
+
* import {generateSecret, InvalidSignatureException, Mac} from 'lakutata/security'
|
|
32
|
+
*
|
|
33
|
+
* Application
|
|
34
|
+
* .run(() => ({
|
|
35
|
+
* id: 'links.app',
|
|
36
|
+
* name: 'Links',
|
|
37
|
+
* providers: {
|
|
38
|
+
* //The keys come from the environment: the new key first, the previous one kept to verify
|
|
39
|
+
* mac: {class: Mac, keys: [process.env.MAC_KEY ?? generateSecret(), process.env.MAC_PREVIOUS_KEY ?? generateSecret()]}
|
|
40
|
+
* }
|
|
41
|
+
* }))
|
|
42
|
+
* .onLaunched(async (app: Application): Promise<void> => {
|
|
43
|
+
* const mac: Mac = await app.getObject('mac')
|
|
44
|
+
* //'user:42.<signature>'
|
|
45
|
+
* const token: string = mac.signMessage('user:42')
|
|
46
|
+
* try {
|
|
47
|
+
* console.log(mac.unsignMessage(token))
|
|
48
|
+
* } catch (error) {
|
|
49
|
+
* if (error instanceof InvalidSignatureException) console.log('Tampered token')
|
|
50
|
+
* }
|
|
51
|
+
* //A detached signature, sent alongside the body
|
|
52
|
+
* const signature: string = mac.sign('{"order":42}')
|
|
53
|
+
* console.log(mac.verify('{"order":42}', signature))
|
|
54
|
+
* })
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
class Mac extends core_1.Provider {
|
|
58
|
+
/**
|
|
59
|
+
* Sign data with the first key.
|
|
60
|
+
* @param data The data to sign: a string (signed as its UTF-8 bytes) or bytes.
|
|
61
|
+
* @returns The HMAC of the data, base64url encoded without padding.
|
|
62
|
+
*/
|
|
63
|
+
sign(data) {
|
|
64
|
+
return (0, HMAC_js_1.hmac)(data, this.keys[0], this.algorithm);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Check a detached signature made by {@link Mac.sign} with any of the keys, in a time that does not depend on the
|
|
68
|
+
* signature or on the key that matches.
|
|
69
|
+
* @param data The signed data, exactly as it was signed (same string or same bytes).
|
|
70
|
+
* @param signature The signature to check, base64url as {@link Mac.sign} returns it.
|
|
71
|
+
* @returns `true` when one of the keys signed the data; `false` otherwise (never throws on a malformed signature).
|
|
72
|
+
*/
|
|
73
|
+
verify(data, signature) {
|
|
74
|
+
//Every key is tried, to not reveal which one signed
|
|
75
|
+
let valid = false;
|
|
76
|
+
for (const key of this.keys)
|
|
77
|
+
valid = (0, HMAC_js_1.verifyHMAC)(data, signature, key, this.algorithm) || valid;
|
|
78
|
+
return valid;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Sign a message and append the signature: `<message>.<signature>`. The message is not encrypted: anyone can read
|
|
82
|
+
* it, only a key holder can change it.
|
|
83
|
+
* @param message The message to sign; it may contain dots (the signature follows the last one).
|
|
84
|
+
* @returns The signed message, to read back with {@link Mac.unsignMessage}.
|
|
85
|
+
*/
|
|
86
|
+
signMessage(message) {
|
|
87
|
+
return `${message}.${this.sign(message)}`;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Check a message signed by {@link Mac.signMessage} with any of the keys, and return it without its signature.
|
|
91
|
+
* @param signedMessage The signed message, `<message>.<signature>`.
|
|
92
|
+
* @returns The original message.
|
|
93
|
+
* @throws {InvalidSignatureException} When the signed message has no dot, or its signature is not valid (the message
|
|
94
|
+
* or the signature was changed, or none of the keys signed it).
|
|
95
|
+
*/
|
|
96
|
+
unsignMessage(signedMessage) {
|
|
97
|
+
const index = signedMessage.lastIndexOf('.');
|
|
98
|
+
if (index >= 0) {
|
|
99
|
+
const message = signedMessage.slice(0, index);
|
|
100
|
+
if (this.verify(message, signedMessage.slice(index + 1)))
|
|
101
|
+
return message;
|
|
102
|
+
}
|
|
103
|
+
throw new InvalidSignatureException_js_1.InvalidSignatureException('The signature of the message is not valid');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
exports.Mac = Mac;
|
|
107
|
+
__decorate([
|
|
108
|
+
(0, di_1.Configurable)(core_1.DTO.Array().items(core_1.DTO.String().min(16)).min(1).required()),
|
|
109
|
+
__metadata("design:type", Array)
|
|
110
|
+
], Mac.prototype, "keys", void 0);
|
|
111
|
+
__decorate([
|
|
112
|
+
(0, di_1.Configurable)(core_1.DTO.String().valid('sha256', 'sha384', 'sha512').optional().default('sha256')),
|
|
113
|
+
__metadata("design:type", String)
|
|
114
|
+
], Mac.prototype, "algorithm", void 0);
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
19
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
20
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
21
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
22
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
23
|
+
};
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
42
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.PasswordHash = void 0;
|
|
46
|
+
const bcrypt = __importStar(require("../lib/bcrypt/index.js"));
|
|
47
|
+
const core_1 = require("@lakutata/core");
|
|
48
|
+
const di_1 = require("@lakutata/core/decorator/di");
|
|
49
|
+
const PasswordTooLongException_js_1 = require("../exceptions/PasswordTooLongException.js");
|
|
50
|
+
/**
|
|
51
|
+
* The largest password bcrypt hashes entirely, in UTF-8 bytes (it ignores the following bytes)
|
|
52
|
+
*/
|
|
53
|
+
const MAX_PASSWORD_BYTES = 72;
|
|
54
|
+
/**
|
|
55
|
+
* A bcrypt hash: its variant, its cost (4 to 31), then its salt and its digest in bcrypt's base64 alphabet
|
|
56
|
+
*/
|
|
57
|
+
const BCRYPT_HASH = /^\$2[aby]\$(0[4-9]|[12]\d|3[01])\$[./A-Za-z0-9]{53}$/;
|
|
58
|
+
/**
|
|
59
|
+
* The password hashing provider: hashes passwords with bcrypt (a new random 16-byte salt per hash, the `$2b$` variant)
|
|
60
|
+
* and checks a password against a stored hash. Use it for user passwords: bcrypt is deliberately slow, so a stolen hash
|
|
61
|
+
* is costly to brute-force. Do not use it to hash tokens or keys (use {@link hmac}) or to encrypt (use {@link Crypt}).
|
|
62
|
+
*
|
|
63
|
+
* Declare it in the `providers` of the application (`{class: PasswordHash, saltRounds: 12}`) and get it with `@Inject`
|
|
64
|
+
* or `getObject()`; it is scoped, like every provider. A hash is a 60-character string (`$2b$10$` then the salt and the
|
|
65
|
+
* digest) holding its own salt and cost, so it is stored as is, in one column, and {@link PasswordHash.validate} needs
|
|
66
|
+
* nothing else; hashes of another cost (or the `$2a$`/`$2y$` variants of other bcrypt libraries) still validate.
|
|
67
|
+
*
|
|
68
|
+
* bcrypt only reads the first 72 bytes of a password (UTF-8): {@link PasswordHash.hash} rejects a longer password with
|
|
69
|
+
* {@link PasswordTooLongException} rather than hashing its prefix, and {@link PasswordHash.validate} gives `false` for
|
|
70
|
+
* it. bcrypt runs in JavaScript on the main thread, in
|
|
71
|
+
* slices of at most 100 ms between which the other requests proceed; a hash or a validation costs about 60 ms of CPU
|
|
72
|
+
* at cost 10 on a current CPU, doubling with each extra round.
|
|
73
|
+
* @example
|
|
74
|
+
* ```typescript
|
|
75
|
+
* import {Application} from 'lakutata'
|
|
76
|
+
* import {PasswordHash, PasswordTooLongException} from 'lakutata/provider/passwordHash'
|
|
77
|
+
*
|
|
78
|
+
* Application
|
|
79
|
+
* .run(() => ({
|
|
80
|
+
* id: 'auth.app',
|
|
81
|
+
* name: 'Auth',
|
|
82
|
+
* providers: {
|
|
83
|
+
* //Each extra round doubles the cost of a hash
|
|
84
|
+
* passwordHash: {class: PasswordHash, saltRounds: 12}
|
|
85
|
+
* }
|
|
86
|
+
* }))
|
|
87
|
+
* .onLaunched(async (app: Application): Promise<void> => {
|
|
88
|
+
* const passwordHash: PasswordHash = await app.getObject('passwordHash')
|
|
89
|
+
* try {
|
|
90
|
+
* //Stored as is: the hash holds its salt and its cost
|
|
91
|
+
* const hash: string = await passwordHash.hash('correct horse battery staple')
|
|
92
|
+
* console.log(await passwordHash.validate('correct horse battery staple', hash))
|
|
93
|
+
* console.log(await passwordHash.validate('wrong password', hash))
|
|
94
|
+
* } catch (error) {
|
|
95
|
+
* if (error instanceof PasswordTooLongException) console.log('Choose a password of 72 bytes at most')
|
|
96
|
+
* }
|
|
97
|
+
* })
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
class PasswordHash extends core_1.Provider {
|
|
101
|
+
/**
|
|
102
|
+
* A new random bcrypt salt with the configured cost (`$2b$10$` and 22 characters), different at each read.
|
|
103
|
+
* @deprecated Not needed: {@link PasswordHash.hash} generates a salt for each hash, and the hash holds it for
|
|
104
|
+
* {@link PasswordHash.validate}.
|
|
105
|
+
*/
|
|
106
|
+
get salt() {
|
|
107
|
+
return bcrypt.genSaltSync(this.saltRounds);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Hash a password with bcrypt, a new random salt and the configured {@link PasswordHash.saltRounds}. Two hashes of
|
|
111
|
+
* the same password differ: compare them with {@link PasswordHash.validate}, never with `===`.
|
|
112
|
+
* @param password The password, as typed by the user: at most 72 bytes once encoded in UTF-8 (72 ASCII
|
|
113
|
+
* characters, fewer with accented letters or emoji). An empty password is hashed too: reject it beforehand if needed.
|
|
114
|
+
* @returns The 60-character bcrypt hash (`$2b$<cost>$<salt><digest>`, bcrypt's own base64 alphabet), to store as is.
|
|
115
|
+
* @throws {PasswordTooLongException} When the password is longer than 72 bytes in UTF-8.
|
|
116
|
+
*/
|
|
117
|
+
async hash(password) {
|
|
118
|
+
//bcrypt ignores the bytes beyond the 72nd: a longer password would match any password sharing its 72 first bytes
|
|
119
|
+
if (bcrypt.truncates(password))
|
|
120
|
+
throw new PasswordTooLongException_js_1.PasswordTooLongException('The password is longer than {max} bytes', { max: MAX_PASSWORD_BYTES });
|
|
121
|
+
return await bcrypt.hash(password, this.saltRounds);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Check a password against a hash made by {@link PasswordHash.hash} (or by another bcrypt library: `$2a$`, `$2b$`,
|
|
125
|
+
* `$2y$`), with the salt and the cost the hash holds; the final comparison takes a constant time.
|
|
126
|
+
* @param password The password to check. A password longer than 72 bytes in UTF-8 gives `false`:
|
|
127
|
+
* {@link PasswordHash.hash} refuses it, so no hash can be its own.
|
|
128
|
+
* @param hash The stored bcrypt hash. A value which is not a bcrypt hash (not 60 characters long, a wrong prefix,
|
|
129
|
+
* cost or alphabet) gives `false`.
|
|
130
|
+
* @returns `true` when the password matches the hash.
|
|
131
|
+
*/
|
|
132
|
+
async validate(password, hash) {
|
|
133
|
+
//Never thrown on a malformed hash: a hash is data read from storage
|
|
134
|
+
if (typeof hash !== 'string' || !BCRYPT_HASH.test(hash))
|
|
135
|
+
return false;
|
|
136
|
+
//bcrypt would compare the first 72 bytes only: a longer password matches no hash made by hash()
|
|
137
|
+
if (bcrypt.truncates(password))
|
|
138
|
+
return false;
|
|
139
|
+
return await bcrypt.compare(password, hash);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
exports.PasswordHash = PasswordHash;
|
|
143
|
+
__decorate([
|
|
144
|
+
(0, di_1.Configurable)(core_1.DTO.Number().integer().min(4).max(31).optional().default(10)),
|
|
145
|
+
__metadata("design:type", Number)
|
|
146
|
+
], PasswordHash.prototype, "saltRounds", void 0);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Exception } from '@lakutata/core';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown by {@link Crypt.decrypt}, {@link Crypt.decryptString}, {@link decryptByKey} and {@link decryptByPassword}
|
|
4
|
+
* when the data cannot be decrypted: a wrong key or password, data changed or truncated, or data not produced by the
|
|
5
|
+
* matching encryption function. The message does not tell these cases apart, on purpose. Its `errno` is
|
|
6
|
+
* `'E_DECRYPTION_FAILED'`; its HTTP status is 500 unless the application handles it.
|
|
7
|
+
*/
|
|
8
|
+
export class DecryptionFailedException extends Exception {
|
|
9
|
+
constructor() {
|
|
10
|
+
super(...arguments);
|
|
11
|
+
/**
|
|
12
|
+
* `'E_DECRYPTION_FAILED'`.
|
|
13
|
+
*/
|
|
14
|
+
this.errno = 'E_DECRYPTION_FAILED';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Exception } from '@lakutata/core';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown by {@link Mac.unsignMessage} when the signed message has no signature or its signature is not valid (the
|
|
4
|
+
* message or the signature was changed, or none of the configured keys signed it); applications may throw it too
|
|
5
|
+
* after {@link verifyHMAC} fails. Its `errno` is `'E_INVALID_SIGNATURE'`; its HTTP status is 500 unless the application
|
|
6
|
+
* handles it.
|
|
7
|
+
*/
|
|
8
|
+
export class InvalidSignatureException extends Exception {
|
|
9
|
+
constructor() {
|
|
10
|
+
super(...arguments);
|
|
11
|
+
/**
|
|
12
|
+
* `'E_INVALID_SIGNATURE'`.
|
|
13
|
+
*/
|
|
14
|
+
this.errno = 'E_INVALID_SIGNATURE';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Exception } from '@lakutata/core';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown by {@link PasswordHash.hash} when the password is longer than 72 bytes once encoded in UTF-8, the most bcrypt
|
|
4
|
+
* reads: hashing only its prefix would let any password sharing those 72 bytes match. Ask the user for a shorter
|
|
5
|
+
* password. Its `errno` is `'E_PASSWORD_TOO_LONG'`; its HTTP status is 500 unless the application handles it.
|
|
6
|
+
*/
|
|
7
|
+
export class PasswordTooLongException extends Exception {
|
|
8
|
+
constructor() {
|
|
9
|
+
super(...arguments);
|
|
10
|
+
/**
|
|
11
|
+
* `'E_PASSWORD_TOO_LONG'`.
|
|
12
|
+
*/
|
|
13
|
+
this.errno = 'E_PASSWORD_TOO_LONG';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Providers
|
|
3
|
+
*/
|
|
4
|
+
export * from '../providers/PasswordHash.js';
|
|
5
|
+
export * from '../providers/Mac.js';
|
|
6
|
+
export * from '../providers/Crypt.js';
|
|
7
|
+
/**
|
|
8
|
+
* Functions
|
|
9
|
+
*/
|
|
10
|
+
export * from '../functions/TimingSafeEqual.js';
|
|
11
|
+
export * from '../functions/GenerateSecret.js';
|
|
12
|
+
export * from '../functions/HMAC.js';
|
|
13
|
+
export * from '../functions/Encryption.js';
|
|
14
|
+
/**
|
|
15
|
+
* Exceptions
|
|
16
|
+
*/
|
|
17
|
+
export * from '../exceptions/PasswordTooLongException.js';
|
|
18
|
+
export * from '../exceptions/InvalidSignatureException.js';
|
|
19
|
+
export * from '../exceptions/DecryptionFailedException.js';
|