@arkstack/common 0.17.25 → 0.18.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/README.md +51 -6
- package/dist/{helpers-E_O2UDRn.d.ts → helpers-Bn-0uv3R.d.ts} +169 -14
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.js +2 -2
- package/dist/{utils-mymPEYd4.js → utils-gKiLoXG9.js} +197 -56
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -515,9 +515,9 @@ Clears all registered hooks.
|
|
|
515
515
|
|
|
516
516
|
**`src/utils/encryption.ts`**
|
|
517
517
|
|
|
518
|
-
AES-256-GCM
|
|
518
|
+
A thin wrapper around [`@arkstack/encryption`](https://www.npmjs.com/package/@arkstack/encryption), bound to the application key. AES-256-GCM for sensitive values (e.g. two-factor authentication secrets); the package underneath runs on the Web Crypto API, so a value encrypted on the server can be decrypted in the browser and vice versa.
|
|
519
519
|
|
|
520
|
-
#### `Encryption.encrypt(value)`
|
|
520
|
+
#### `Encryption.encrypt(value, key?)`
|
|
521
521
|
|
|
522
522
|
Encrypts a string. Returns a colon-delimited base64url string: `<iv>:<authTag>:<ciphertext>`.
|
|
523
523
|
|
|
@@ -528,7 +528,9 @@ const token = Encryption.encrypt('my-secret-value');
|
|
|
528
528
|
// "abc123:def456:ghi789"
|
|
529
529
|
```
|
|
530
530
|
|
|
531
|
-
|
|
531
|
+
---
|
|
532
|
+
|
|
533
|
+
#### `Encryption.decrypt(payload, key?)`
|
|
532
534
|
|
|
533
535
|
Decrypts a payload produced by `encrypt`. Throws if the format is invalid or the key is wrong.
|
|
534
536
|
|
|
@@ -537,11 +539,54 @@ const original = Encryption.decrypt(token);
|
|
|
537
539
|
// "my-secret-value"
|
|
538
540
|
```
|
|
539
541
|
|
|
542
|
+
---
|
|
543
|
+
|
|
544
|
+
#### `Encryption.encryptAsync(value, key?, options?)` / `Encryption.decryptAsync(payload, key?, options?)`
|
|
545
|
+
|
|
546
|
+
The same operations on the Web Crypto path, in the same payload format, so the two can be mixed freely. Use these when the calling code is (or may become) shared with the browser. `options.aad` binds additional authenticated data to the ciphertext.
|
|
547
|
+
|
|
548
|
+
---
|
|
549
|
+
|
|
550
|
+
#### `Encryption.cipher(key?)`
|
|
551
|
+
|
|
552
|
+
A `Cipher` bound to the application key, for encrypting many values without re-deriving the key, and for raw bytes via `encryptBytes` / `decryptBytes`.
|
|
553
|
+
|
|
554
|
+
---
|
|
555
|
+
|
|
556
|
+
#### Key utilities
|
|
557
|
+
|
|
558
|
+
```ts
|
|
559
|
+
Encryption.generateKey(); // random base64url key
|
|
560
|
+
await Encryption.generateKeyPair(); // { publicKey, privateKey } ECDH identity
|
|
561
|
+
await Encryption.deriveKey(password); // PBKDF2 → { key, salt, iterations }
|
|
562
|
+
await Encryption.compareKeys(left, right); // constant time
|
|
563
|
+
await Encryption.fingerprint(); // displayable digest of the app key
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
---
|
|
567
|
+
|
|
568
|
+
#### End-to-end encryption
|
|
569
|
+
|
|
570
|
+
```ts
|
|
571
|
+
const channel = await Encryption.channel(myPrivateKey, peerPublicKey);
|
|
572
|
+
|
|
573
|
+
await channel.decrypt(await channel.encrypt('hey'));
|
|
574
|
+
|
|
575
|
+
await Encryption.seal('anonymous tip', peerPublicKey);
|
|
576
|
+
await Encryption.open(payload, myPrivateKey);
|
|
577
|
+
|
|
578
|
+
await Encryption.safetyNumber(myPublicKey, peerPublicKey);
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
The full `@arkstack/encryption` surface — `Cipher`, `Codec`, `EncryptionKey`, `KeyPair`, `Keys`, `SealedBox`, `SecureChannel`, `NodeCipher` — is re-exported from this package.
|
|
582
|
+
|
|
540
583
|
**Environment variable:**
|
|
541
584
|
|
|
542
|
-
| Variable
|
|
543
|
-
|
|
|
544
|
-
| `
|
|
585
|
+
| Variable | Required | Description |
|
|
586
|
+
| --------- | -------- | ---------------------------------------------------------- |
|
|
587
|
+
| `APP_KEY` | Yes | Raw secret; hashed to a 256-bit key internally via SHA-256 |
|
|
588
|
+
|
|
589
|
+
Generate one with `ark key:generate`. The legacy `TWO_FACTOR_ENCRYPTION_KEY` is still honored when `APP_KEY` is not set.
|
|
545
590
|
|
|
546
591
|
---
|
|
547
592
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { ChannelOptions, Cipher, CipherOptions, Codec, DeriveOptions, DerivedKey, EncryptionKey, FingerprintOptions, KeyInput, KeyPair, Keys, SealedBox, SecureChannel, SerializedKeyPair } from "@arkstack/encryption";
|
|
2
|
+
import { NodeCipher } from "@arkstack/encryption/node";
|
|
1
3
|
import { JitiOptions, JitiResolveOptions } from "jiti";
|
|
2
4
|
import { TOTP } from "otpauth";
|
|
5
|
+
import { Model, ModelStatic, RegisteredModelClass, RegisteredModelName, RelatedModelClass } from "arkormx";
|
|
3
6
|
import { ChalkInstance } from "chalk";
|
|
4
|
-
import { Model, ModelStatic } from "arkormx";
|
|
5
7
|
//#region src/Logger.d.ts
|
|
6
8
|
declare class Console {
|
|
7
9
|
static log: (...args: any[]) => string | void;
|
|
@@ -388,11 +390,160 @@ interface PublishConfirmation {
|
|
|
388
390
|
}
|
|
389
391
|
//#endregion
|
|
390
392
|
//#region src/utils/encryption.d.ts
|
|
393
|
+
/**
|
|
394
|
+
* Application facing encryption, bound to the app key.
|
|
395
|
+
*
|
|
396
|
+
* This is a thin wrapper over `@arkstack/encryption`. `encrypt()` and
|
|
397
|
+
* `decrypt()` keep the synchronous signatures and the exact payload format
|
|
398
|
+
* they have always had — `<iv>:<authTag>:<ciphertext>`, AES-256-GCM under
|
|
399
|
+
* SHA-256 of `APP_KEY` — so existing ciphertexts and call sites are unaffected.
|
|
400
|
+
*
|
|
401
|
+
* Everything else is new surface: the asynchronous methods run on the Web
|
|
402
|
+
* Crypto API, which means a browser holding the same key (or the same key pair
|
|
403
|
+
* peer) can decrypt what the server wrote, and the server can decrypt what the
|
|
404
|
+
* browser wrote.
|
|
405
|
+
*/
|
|
391
406
|
declare class Encryption {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
407
|
+
/**
|
|
408
|
+
* Encrypt a string with the application key.
|
|
409
|
+
*
|
|
410
|
+
* @param value
|
|
411
|
+
* @param key Override the application key for this call.
|
|
412
|
+
* @returns
|
|
413
|
+
*/
|
|
414
|
+
static encrypt(value: string, key?: KeyInput): string;
|
|
415
|
+
/**
|
|
416
|
+
* Decrypt a payload produced by {@link encrypt}.
|
|
417
|
+
*
|
|
418
|
+
* @param payload
|
|
419
|
+
* @param key Override the application key for this call.
|
|
420
|
+
* @returns
|
|
421
|
+
*/
|
|
422
|
+
static decrypt(payload: string, key?: KeyInput): string;
|
|
423
|
+
/**
|
|
424
|
+
* Encrypt through the isomorphic Web Crypto implementation.
|
|
425
|
+
*
|
|
426
|
+
* Produces the same payload format as {@link encrypt}; use it when the
|
|
427
|
+
* calling code is (or may become) shared with the browser.
|
|
428
|
+
*
|
|
429
|
+
* @param value
|
|
430
|
+
* @param key
|
|
431
|
+
* @param options
|
|
432
|
+
* @returns
|
|
433
|
+
*/
|
|
434
|
+
static encryptAsync(value: string, key?: KeyInput, options?: CipherOptions): Promise<string>;
|
|
435
|
+
/**
|
|
436
|
+
* Decrypt through the isomorphic Web Crypto implementation.
|
|
437
|
+
*
|
|
438
|
+
* @param payload
|
|
439
|
+
* @param key
|
|
440
|
+
* @param options
|
|
441
|
+
* @returns
|
|
442
|
+
*/
|
|
443
|
+
static decryptAsync(payload: string, key?: KeyInput, options?: CipherOptions): Promise<string>;
|
|
444
|
+
/**
|
|
445
|
+
* A cipher bound to the application key, for encrypting many values or raw
|
|
446
|
+
* bytes without re-deriving the key each time.
|
|
447
|
+
*
|
|
448
|
+
* @param key
|
|
449
|
+
* @returns
|
|
450
|
+
*/
|
|
451
|
+
static cipher(key?: KeyInput): Promise<Cipher>;
|
|
452
|
+
/**
|
|
453
|
+
* The application key as it appears in the environment.
|
|
454
|
+
*
|
|
455
|
+
* Reads `APP_KEY`, falling back to the legacy `TWO_FACTOR_ENCRYPTION_KEY`
|
|
456
|
+
* variable. Override this in a subclass to source the key elsewhere.
|
|
457
|
+
*
|
|
458
|
+
* @returns
|
|
459
|
+
*/
|
|
460
|
+
protected static secret(): string;
|
|
461
|
+
/**
|
|
462
|
+
* The 32 bytes of key material actually handed to the cipher: SHA-256 of
|
|
463
|
+
* the application key, or of an explicit override.
|
|
464
|
+
*
|
|
465
|
+
* The same bytes are reachable in the browser with
|
|
466
|
+
* `EncryptionKey.fromSecret(secret)`.
|
|
467
|
+
*
|
|
468
|
+
* @param key
|
|
469
|
+
* @returns
|
|
470
|
+
*/
|
|
471
|
+
static material(key?: KeyInput): Uint8Array;
|
|
472
|
+
/**
|
|
473
|
+
* Generate a random base64url encryption key.
|
|
474
|
+
*
|
|
475
|
+
* @param length Key length in bytes, defaults to 32.
|
|
476
|
+
* @returns
|
|
477
|
+
*/
|
|
478
|
+
static generateKey(length?: number): string;
|
|
479
|
+
/**
|
|
480
|
+
* Generate an end-to-end encryption identity. The public key is published,
|
|
481
|
+
* the private key stays with its owner.
|
|
482
|
+
*
|
|
483
|
+
* @returns
|
|
484
|
+
*/
|
|
485
|
+
static generateKeyPair(): Promise<SerializedKeyPair>;
|
|
486
|
+
/**
|
|
487
|
+
* Stretch a user supplied password into a key with PBKDF2-HMAC-SHA256.
|
|
488
|
+
*
|
|
489
|
+
* @param password
|
|
490
|
+
* @param options
|
|
491
|
+
* @returns
|
|
492
|
+
*/
|
|
493
|
+
static deriveKey(password: string, options?: DeriveOptions): Promise<DerivedKey>;
|
|
494
|
+
/**
|
|
495
|
+
* Constant time comparison of two keys.
|
|
496
|
+
*
|
|
497
|
+
* @param left
|
|
498
|
+
* @param right
|
|
499
|
+
* @returns
|
|
500
|
+
*/
|
|
501
|
+
static compareKeys(left: KeyInput, right: KeyInput): Promise<boolean>;
|
|
502
|
+
/**
|
|
503
|
+
* A displayable digest of a key, safe to show to users or write to logs.
|
|
504
|
+
*
|
|
505
|
+
* @param key Defaults to the application key.
|
|
506
|
+
* @param options
|
|
507
|
+
* @returns
|
|
508
|
+
*/
|
|
509
|
+
static fingerprint(key?: KeyInput, options?: FingerprintOptions): Promise<string>;
|
|
510
|
+
/**
|
|
511
|
+
* Open an end-to-end encrypted channel between a local private key and a
|
|
512
|
+
* peer's public key. Neither key, nor the secret they agree on, ever
|
|
513
|
+
* crosses the wire.
|
|
514
|
+
*
|
|
515
|
+
* @param privateKey
|
|
516
|
+
* @param peerPublicKey
|
|
517
|
+
* @param options
|
|
518
|
+
* @returns
|
|
519
|
+
*/
|
|
520
|
+
static channel(privateKey: string | KeyPair, peerPublicKey: string | KeyPair, options?: ChannelOptions): Promise<SecureChannel>;
|
|
521
|
+
/**
|
|
522
|
+
* Encrypt a message to a public key without needing a sender identity.
|
|
523
|
+
*
|
|
524
|
+
* @param message
|
|
525
|
+
* @param recipientPublicKey
|
|
526
|
+
* @returns
|
|
527
|
+
*/
|
|
528
|
+
static seal(message: string, recipientPublicKey: string | KeyPair): Promise<string>;
|
|
529
|
+
/**
|
|
530
|
+
* Open a payload produced by {@link seal}.
|
|
531
|
+
*
|
|
532
|
+
* @param payload
|
|
533
|
+
* @param recipientPrivateKey
|
|
534
|
+
* @returns
|
|
535
|
+
*/
|
|
536
|
+
static open(payload: string, recipientPrivateKey: string | KeyPair): Promise<string>;
|
|
537
|
+
/**
|
|
538
|
+
* The safety number for a conversation between two public keys — show it to
|
|
539
|
+
* both participants so they can verify nobody swapped a key in transit.
|
|
540
|
+
*
|
|
541
|
+
* @param first
|
|
542
|
+
* @param second
|
|
543
|
+
* @param groups
|
|
544
|
+
* @returns
|
|
545
|
+
*/
|
|
546
|
+
static safetyNumber(first: string, second: string, groups?: number): Promise<string>;
|
|
396
547
|
}
|
|
397
548
|
//#endregion
|
|
398
549
|
//#region src/utils/hash.d.ts
|
|
@@ -428,7 +579,6 @@ declare class Hash {
|
|
|
428
579
|
type AbstractModelConstructor<TModel = unknown> = abstract new (attributes?: Record<string, unknown>) => TModel;
|
|
429
580
|
type ModelConstructor<TModel extends Model = Model> = AbstractModelConstructor<TModel> & Pick<ModelStatic<TModel>, keyof ModelStatic<TModel>>;
|
|
430
581
|
interface ModelRegistry {}
|
|
431
|
-
type ModelName = Extract<keyof ModelRegistry, string>;
|
|
432
582
|
/**
|
|
433
583
|
* Checks and asserts if target is a class
|
|
434
584
|
*
|
|
@@ -474,15 +624,18 @@ declare const resolvePagination: (query: {
|
|
|
474
624
|
maxPerPage?: number;
|
|
475
625
|
}) => PaginationOptions;
|
|
476
626
|
/**
|
|
477
|
-
*
|
|
627
|
+
* Synchronously resolve an application model by name.
|
|
478
628
|
*
|
|
479
|
-
*
|
|
480
|
-
*
|
|
629
|
+
* Registered models are returned first. If a model has not been registered yet,
|
|
630
|
+
* ArkORM loads it from the configured models paths, registers it, and returns
|
|
631
|
+
* the matching constructor.
|
|
481
632
|
*
|
|
482
633
|
* @param modelName
|
|
634
|
+
* @alias {@link getArkormxModel}
|
|
635
|
+
* @returns
|
|
483
636
|
*/
|
|
484
|
-
declare function getModel<TName extends
|
|
485
|
-
declare function getModel<TModel extends
|
|
637
|
+
declare function getModel$1<TName extends RegisteredModelName>(modelName: TName): RegisteredModelClass<TName>;
|
|
638
|
+
declare function getModel$1<TModel extends RelatedModelClass = RelatedModelClass>(modelName: string): TModel;
|
|
486
639
|
/**
|
|
487
640
|
* Synchronously import an application model by name.
|
|
488
641
|
*
|
|
@@ -490,9 +643,11 @@ declare function getModel<TModel extends AbstractModelConstructor = ModelConstru
|
|
|
490
643
|
* Without a registry entry, pass the class type explicitly: `getModel<typeof User>('User')`.
|
|
491
644
|
*
|
|
492
645
|
* @param modelName
|
|
646
|
+
* @alias {@link getArkormxModel}
|
|
647
|
+
* @deprecated 0.17.27 - Use {@link getModel} or {@link getArkormxModel}
|
|
493
648
|
*/
|
|
494
|
-
declare function getModelSync<TName extends
|
|
495
|
-
declare function getModelSync<TModel extends
|
|
649
|
+
declare function getModelSync<TName extends RegisteredModelName>(modelName: TName): RegisteredModelClass<TName>;
|
|
650
|
+
declare function getModelSync<TModel extends RelatedModelClass = RelatedModelClass>(modelName: string): TModel;
|
|
496
651
|
declare const initializeGlobalContext: ({ Request, Response, Session }?: {
|
|
497
652
|
Request?: any;
|
|
498
653
|
Response?: any;
|
|
@@ -525,4 +680,4 @@ declare const abortIf: <T>(boolean: T, message?: string, code?: number) => asser
|
|
|
525
680
|
*/
|
|
526
681
|
declare const assertFound: <T>(value: T | null | undefined, message: string, code?: number) => asserts value is T;
|
|
527
682
|
//#endregion
|
|
528
|
-
export {
|
|
683
|
+
export { IHook as $, SerializedKeyPair as A, EnvKey as B, FingerprintOptions as C, NodeCipher as D, Keys as E, Choices as F, GlobalConfig as G, EnvRegistry as H, ConfigRegistry as I, HookFor as J, GlobalEnv as K, ConfigShape as L, ArkstackErrorPayload as M, ArkstackErrorShape as N, SealedBox as O, Choice as P, HookRegistry as Q, DotPath as R, EncryptionKey as S, KeyPair as T, EnvReturn as U, EnvLookup as V, FileImporter as W, HookPos as X, HookName as Y, HookPositions as Z, CipherOptions as _, abortIf as a, Primitive as at, DerivedKey as b, getModelSync as c, PublishFilter as ct, normalizePositiveInteger as d, Logger as dt, LoggerChalk as et, perPage as f, Cipher as g, ChannelOptions as h, abort as i, PaginationOptions as it, AppConfig as j, SecureChannel as k, initializeGlobalContext as l, PublishGroup as lt, Hash as m, ModelConstructor as n, LoggerParseSignature as nt, assertFound as o, PublishConfirmation as ot, resolvePagination as p, HookArgs as q, ModelRegistry as r, MergedConfig as rt, getModel$1 as s, PublishEntry as st, AbstractModelConstructor as t, LoggerLog as tt, isClass as u, UnionToIntersection as ut, Codec as v, KeyInput as w, Encryption as x, DeriveOptions as y, DotPathValue as z };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference path="./app.d.ts" />
|
|
2
|
-
import { A as
|
|
2
|
+
import { $ as IHook, A as SerializedKeyPair, B as EnvKey, C as FingerprintOptions, D as NodeCipher, E as Keys, F as Choices, G as GlobalConfig, H as EnvRegistry, I as ConfigRegistry, J as HookFor, K as GlobalEnv, L as ConfigShape, M as ArkstackErrorPayload, N as ArkstackErrorShape, O as SealedBox, P as Choice, Q as HookRegistry, R as DotPath, S as EncryptionKey, T as KeyPair, U as EnvReturn, V as EnvLookup, W as FileImporter, X as HookPos, Y as HookName, Z as HookPositions, _ as CipherOptions, a as abortIf, at as Primitive, b as DerivedKey, c as getModelSync, ct as PublishFilter, d as normalizePositiveInteger, dt as Logger, et as LoggerChalk, f as perPage, g as Cipher, h as ChannelOptions, i as abort, it as PaginationOptions, j as AppConfig, k as SecureChannel, l as initializeGlobalContext, lt as PublishGroup, m as Hash, n as ModelConstructor, nt as LoggerParseSignature, o as assertFound, ot as PublishConfirmation, p as resolvePagination, q as HookArgs, r as ModelRegistry, rt as MergedConfig, s as getModel, st as PublishEntry, t as AbstractModelConstructor, tt as LoggerLog, u as isClass, ut as UnionToIntersection, v as Codec, w as KeyInput, x as Encryption, y as DeriveOptions, z as DotPathValue } from "./helpers-Bn-0uv3R.js";
|
|
3
3
|
import { Arkstack } from "@arkstack/contract";
|
|
4
4
|
import { ForkOptions } from "node:child_process";
|
|
5
5
|
import pino from "pino";
|
|
@@ -514,4 +514,4 @@ declare const devServer: (options: DevServerRunnerOptions) => {
|
|
|
514
514
|
closeWatcher(): Promise<void>;
|
|
515
515
|
};
|
|
516
516
|
//#endregion
|
|
517
|
-
export { AbstractModelConstructor, AppConfig, AppException, ArkstackErrorPayload, ArkstackErrorShape, CONFIG_KEY, Choice, Choices, ConfigLoader, ConfigRegistry, ConfigShape, DevServerRunnerOptions, DotPath, DotPathValue, Encryption, EnvKey, EnvLoader, EnvLookup, EnvRegistry, EnvReturn, ErrorHandler, Exception, FileImporter, GlobalConfig, GlobalEnv, Hash, Hook, HookArgs, HookFor, HookName, HookPos, HookPositions, HookRegistry, IHook, Logger, LoggerChalk, LoggerLog, LoggerParseSignature, MergedConfig, ModelConstructor, ModelRegistry, PaginationOptions, Primitive, PublishConfirmation, PublishEntry, PublishFilter, PublishGroup, Publisher, RequestException, TlsCredentials, UnionToIntersection, abort, abortIf, appKey, appUrl, assertFound, bindGracefulShutdown, bootWithDetectedPort, config, configLoader, createErrorPayload, devServer, devTlsCredentials, discoverCommands, env, envLoader, getErrorLogger, getModel, getModelSync, getPrimaryError, getValidationErrors, importFile, initializeGlobalContext, interopDefault, isClass, isModelNotFoundError, isValidationError, loadPrototypes, localNetworkAddress, logUnhandledError, nodeEnv, normalizePositiveInteger, normalizeStatusCode, outputDir, perPage, rebuildOutput, renderError, resolvePagination, resolveRuntimeDir, resolveRuntimeModule, serializeError, shouldHideStack, shouldLogError, toErrorShape, toOutputPath };
|
|
517
|
+
export { AbstractModelConstructor, AppConfig, AppException, ArkstackErrorPayload, ArkstackErrorShape, CONFIG_KEY, type ChannelOptions, Choice, Choices, Cipher, type CipherOptions, Codec, ConfigLoader, ConfigRegistry, ConfigShape, type DeriveOptions, type DerivedKey, DevServerRunnerOptions, DotPath, DotPathValue, Encryption, EncryptionKey, EnvKey, EnvLoader, EnvLookup, EnvRegistry, EnvReturn, ErrorHandler, Exception, FileImporter, type FingerprintOptions, GlobalConfig, GlobalEnv, Hash, Hook, HookArgs, HookFor, HookName, HookPos, HookPositions, HookRegistry, IHook, type KeyInput, KeyPair, Keys, Logger, LoggerChalk, LoggerLog, LoggerParseSignature, MergedConfig, ModelConstructor, ModelRegistry, NodeCipher, PaginationOptions, Primitive, PublishConfirmation, PublishEntry, PublishFilter, PublishGroup, Publisher, RequestException, SealedBox, SecureChannel, type SerializedKeyPair, TlsCredentials, UnionToIntersection, abort, abortIf, appKey, appUrl, assertFound, bindGracefulShutdown, bootWithDetectedPort, config, configLoader, createErrorPayload, devServer, devTlsCredentials, discoverCommands, env, envLoader, getErrorLogger, getModel, getModelSync, getPrimaryError, getValidationErrors, importFile, initializeGlobalContext, interopDefault, isClass, isModelNotFoundError, isValidationError, loadPrototypes, localNetworkAddress, logUnhandledError, nodeEnv, normalizePositiveInteger, normalizeStatusCode, outputDir, perPage, rebuildOutput, renderError, resolvePagination, resolveRuntimeDir, resolveRuntimeModule, serializeError, shouldHideStack, shouldLogError, toErrorShape, toOutputPath };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { _ as EnvLoader, a as env, c as nodeEnv, d as resolveRuntimeDir, f as resolveRuntimeModule, g as configLoader, h as ConfigLoader, i as discoverCommands, l as outputDir, m as CONFIG_KEY, n as appUrl, o as importFile, p as toOutputPath, r as config, s as interopDefault, t as appKey, u as rebuildOutput, v as envLoader } from "./system-Die_Dv5y.js";
|
|
2
|
-
import { _ as RequestException, b as Hash, c as abortIf, d as getModelSync, f as initializeGlobalContext, g as resolvePagination, h as perPage, l as assertFound, m as normalizePositiveInteger, p as isClass, s as abort, u as getModel, v as AppException, x as
|
|
2
|
+
import { C as Encryption, D as NodeCipher, E as Keys, O as SealedBox, S as Codec, T as KeyPair, _ as RequestException, b as Hash, c as abortIf, d as getModelSync, f as initializeGlobalContext, g as resolvePagination, h as perPage, k as SecureChannel, l as assertFound, m as normalizePositiveInteger, p as isClass, s as abort, u as getModel, v as AppException, w as EncryptionKey, x as Cipher, y as Exception } from "./utils-gKiLoXG9.js";
|
|
3
3
|
import { Hook as Hook$1 } from "@arkstack/foundry";
|
|
4
4
|
import { Arkstack } from "@arkstack/contract";
|
|
5
5
|
import { str } from "@h3ravel/support";
|
|
@@ -660,4 +660,4 @@ const devServer = (options) => {
|
|
|
660
660
|
};
|
|
661
661
|
};
|
|
662
662
|
//#endregion
|
|
663
|
-
export { AppException, CONFIG_KEY, ConfigLoader, Encryption, EnvLoader, ErrorHandler, Exception, Hash, Hook, Logger, Publisher, RequestException, abort, abortIf, appKey, appUrl, assertFound, bindGracefulShutdown, bootWithDetectedPort, config, configLoader, createErrorPayload, devServer, devTlsCredentials, discoverCommands, env, envLoader, getErrorLogger, getModel, getModelSync, getPrimaryError, getValidationErrors, importFile, initializeGlobalContext, interopDefault, isClass, isModelNotFoundError, isValidationError, loadPrototypes, localNetworkAddress, logUnhandledError, nodeEnv, normalizePositiveInteger, normalizeStatusCode, outputDir, perPage, rebuildOutput, renderError, resolvePagination, resolveRuntimeDir, resolveRuntimeModule, serializeError, shouldHideStack, shouldLogError, toErrorShape, toOutputPath };
|
|
663
|
+
export { AppException, CONFIG_KEY, Cipher, Codec, ConfigLoader, Encryption, EncryptionKey, EnvLoader, ErrorHandler, Exception, Hash, Hook, KeyPair, Keys, Logger, NodeCipher, Publisher, RequestException, SealedBox, SecureChannel, abort, abortIf, appKey, appUrl, assertFound, bindGracefulShutdown, bootWithDetectedPort, config, configLoader, createErrorPayload, devServer, devTlsCredentials, discoverCommands, env, envLoader, getErrorLogger, getModel, getModelSync, getPrimaryError, getValidationErrors, importFile, initializeGlobalContext, interopDefault, isClass, isModelNotFoundError, isValidationError, loadPrototypes, localNetworkAddress, logUnhandledError, nodeEnv, normalizePositiveInteger, normalizeStatusCode, outputDir, perPage, rebuildOutput, renderError, resolvePagination, resolveRuntimeDir, resolveRuntimeModule, serializeError, shouldHideStack, shouldLogError, toErrorShape, toOutputPath };
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as abortIf, c as getModelSync, d as normalizePositiveInteger, f as perPage, h as
|
|
1
|
+
import { A as SerializedKeyPair, C as FingerprintOptions, D as NodeCipher, E as Keys, O as SealedBox, S as EncryptionKey, T as KeyPair, _ as CipherOptions, a as abortIf, b as DerivedKey, c as getModelSync, d as normalizePositiveInteger, f as perPage, g as Cipher, h as ChannelOptions, i as abort, k as SecureChannel, l as initializeGlobalContext, m as Hash, n as ModelConstructor, o as assertFound, p as resolvePagination, r as ModelRegistry, s as getModel, t as AbstractModelConstructor, u as isClass, v as Codec, w as KeyInput, x as Encryption, y as DeriveOptions } from "../helpers-Bn-0uv3R.js";
|
|
2
2
|
import { Model } from "arkormx";
|
|
3
3
|
//#region src/utils/traits.d.ts
|
|
4
4
|
declare const crc32: (str: string) => number;
|
|
@@ -184,4 +184,4 @@ type Derived<T extends (Trait | TypeFactory<Trait> | Cons)> = T extends TypeFact
|
|
|
184
184
|
*/
|
|
185
185
|
declare function uses<T extends (Trait | TypeFactory<Trait> | Cons)>(instance: unknown, trait: T): instance is Derived<T>;
|
|
186
186
|
//#endregion
|
|
187
|
-
export { AbstractModelConstructor, Derived, Encryption, Hash, ModelConstructor, ModelRegistry, Trait, abort, abortIf, assertFound, callTraitMethods, crc32, getModel, getModelSync, getTraitMethods, initializeGlobalContext, isClass, normalizePositiveInteger, perPage, resolvePagination, trait, use, uses };
|
|
187
|
+
export { AbstractModelConstructor, type ChannelOptions, Cipher, type CipherOptions, Codec, type DeriveOptions, Derived, type DerivedKey, Encryption, EncryptionKey, type FingerprintOptions, Hash, type KeyInput, KeyPair, Keys, ModelConstructor, ModelRegistry, NodeCipher, SealedBox, SecureChannel, type SerializedKeyPair, Trait, abort, abortIf, assertFound, callTraitMethods, crc32, getModel, getModelSync, getTraitMethods, initializeGlobalContext, isClass, normalizePositiveInteger, perPage, resolvePagination, trait, use, uses };
|
package/dist/utils/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as use, b as Hash, c as abortIf, d as getModelSync, f as initializeGlobalContext, g as resolvePagination, h as perPage, i as trait, l as assertFound, m as normalizePositiveInteger, n as crc32, o as uses, p as isClass, r as getTraitMethods, s as abort, t as callTraitMethods, u as getModel, x as
|
|
2
|
-
export { Encryption, Hash, abort, abortIf, assertFound, callTraitMethods, crc32, getModel, getModelSync, getTraitMethods, initializeGlobalContext, isClass, normalizePositiveInteger, perPage, resolvePagination, trait, use, uses };
|
|
1
|
+
import { C as Encryption, D as NodeCipher, E as Keys, O as SealedBox, S as Codec, T as KeyPair, a as use, b as Hash, c as abortIf, d as getModelSync, f as initializeGlobalContext, g as resolvePagination, h as perPage, i as trait, k as SecureChannel, l as assertFound, m as normalizePositiveInteger, n as crc32, o as uses, p as isClass, r as getTraitMethods, s as abort, t as callTraitMethods, u as getModel, w as EncryptionKey, x as Cipher } from "../utils-gKiLoXG9.js";
|
|
2
|
+
export { Cipher, Codec, Encryption, EncryptionKey, Hash, KeyPair, Keys, NodeCipher, SealedBox, SecureChannel, abort, abortIf, assertFound, callTraitMethods, crc32, getModel, getModelSync, getTraitMethods, initializeGlobalContext, isClass, normalizePositiveInteger, perPage, resolvePagination, trait, use, uses };
|
|
@@ -1,34 +1,201 @@
|
|
|
1
|
-
import { a as env,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { Arkstack } from "@arkstack/contract";
|
|
5
|
-
import path from "node:path";
|
|
1
|
+
import { a as env, t as appKey } from "./system-Die_Dv5y.js";
|
|
2
|
+
import { Cipher, Codec, EncryptionKey, KeyPair, Keys, SealedBox, SecureChannel } from "@arkstack/encryption";
|
|
3
|
+
import { NodeCipher } from "@arkstack/encryption/node";
|
|
6
4
|
import { Secret, TOTP } from "otpauth";
|
|
7
5
|
import { compare, genSalt, hash } from "bcryptjs";
|
|
6
|
+
import { getModel } from "arkormx";
|
|
8
7
|
//#region src/utils/encryption.ts
|
|
8
|
+
/**
|
|
9
|
+
* Application facing encryption, bound to the app key.
|
|
10
|
+
*
|
|
11
|
+
* This is a thin wrapper over `@arkstack/encryption`. `encrypt()` and
|
|
12
|
+
* `decrypt()` keep the synchronous signatures and the exact payload format
|
|
13
|
+
* they have always had — `<iv>:<authTag>:<ciphertext>`, AES-256-GCM under
|
|
14
|
+
* SHA-256 of `APP_KEY` — so existing ciphertexts and call sites are unaffected.
|
|
15
|
+
*
|
|
16
|
+
* Everything else is new surface: the asynchronous methods run on the Web
|
|
17
|
+
* Crypto API, which means a browser holding the same key (or the same key pair
|
|
18
|
+
* peer) can decrypt what the server wrote, and the server can decrypt what the
|
|
19
|
+
* browser wrote.
|
|
20
|
+
*/
|
|
9
21
|
var Encryption = class {
|
|
10
|
-
|
|
11
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Encrypt a string with the application key.
|
|
24
|
+
*
|
|
25
|
+
* @param value
|
|
26
|
+
* @param key Override the application key for this call.
|
|
27
|
+
* @returns
|
|
28
|
+
*/
|
|
29
|
+
static encrypt(value, key) {
|
|
30
|
+
return NodeCipher.encrypt(value, this.material(key));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Decrypt a payload produced by {@link encrypt}.
|
|
34
|
+
*
|
|
35
|
+
* @param payload
|
|
36
|
+
* @param key Override the application key for this call.
|
|
37
|
+
* @returns
|
|
38
|
+
*/
|
|
39
|
+
static decrypt(payload, key) {
|
|
40
|
+
return NodeCipher.decrypt(payload, this.material(key));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Encrypt through the isomorphic Web Crypto implementation.
|
|
44
|
+
*
|
|
45
|
+
* Produces the same payload format as {@link encrypt}; use it when the
|
|
46
|
+
* calling code is (or may become) shared with the browser.
|
|
47
|
+
*
|
|
48
|
+
* @param value
|
|
49
|
+
* @param key
|
|
50
|
+
* @param options
|
|
51
|
+
* @returns
|
|
52
|
+
*/
|
|
53
|
+
static async encryptAsync(value, key, options = {}) {
|
|
54
|
+
return await (await this.cipher(key)).encrypt(value, options);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Decrypt through the isomorphic Web Crypto implementation.
|
|
58
|
+
*
|
|
59
|
+
* @param payload
|
|
60
|
+
* @param key
|
|
61
|
+
* @param options
|
|
62
|
+
* @returns
|
|
63
|
+
*/
|
|
64
|
+
static async decryptAsync(payload, key, options = {}) {
|
|
65
|
+
return await (await this.cipher(key)).decrypt(payload, options);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A cipher bound to the application key, for encrypting many values or raw
|
|
69
|
+
* bytes without re-deriving the key each time.
|
|
70
|
+
*
|
|
71
|
+
* @param key
|
|
72
|
+
* @returns
|
|
73
|
+
*/
|
|
74
|
+
static async cipher(key) {
|
|
75
|
+
return key === void 0 ? new Cipher(new EncryptionKey(this.material())) : await Cipher.from(key);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The application key as it appears in the environment.
|
|
79
|
+
*
|
|
80
|
+
* Reads `APP_KEY`, falling back to the legacy `TWO_FACTOR_ENCRYPTION_KEY`
|
|
81
|
+
* variable. Override this in a subclass to source the key elsewhere.
|
|
82
|
+
*
|
|
83
|
+
* @returns
|
|
84
|
+
*/
|
|
85
|
+
static secret() {
|
|
12
86
|
const secret = appKey("TWO_FACTOR_ENCRYPTION_KEY");
|
|
13
|
-
if (!secret) throw new Error("APP_KEY is required to use
|
|
14
|
-
return
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
static
|
|
27
|
-
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
87
|
+
if (!secret) throw new Error("APP_KEY is required to use Encryption. Run `ark key:generate`.");
|
|
88
|
+
return secret;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The 32 bytes of key material actually handed to the cipher: SHA-256 of
|
|
92
|
+
* the application key, or of an explicit override.
|
|
93
|
+
*
|
|
94
|
+
* The same bytes are reachable in the browser with
|
|
95
|
+
* `EncryptionKey.fromSecret(secret)`.
|
|
96
|
+
*
|
|
97
|
+
* @param key
|
|
98
|
+
* @returns
|
|
99
|
+
*/
|
|
100
|
+
static material(key) {
|
|
101
|
+
if (key === void 0) return NodeCipher.fromSecret(this.secret());
|
|
102
|
+
if (key instanceof EncryptionKey) return key.bytes;
|
|
103
|
+
if (key instanceof Uint8Array) return key;
|
|
104
|
+
if (typeof key === "string") return NodeCipher.resolve(key);
|
|
105
|
+
throw new TypeError("A CryptoKey cannot be used with the synchronous cipher; pass raw key material instead");
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Generate a random base64url encryption key.
|
|
109
|
+
*
|
|
110
|
+
* @param length Key length in bytes, defaults to 32.
|
|
111
|
+
* @returns
|
|
112
|
+
*/
|
|
113
|
+
static generateKey(length = 32) {
|
|
114
|
+
return Keys.generateString(length);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Generate an end-to-end encryption identity. The public key is published,
|
|
118
|
+
* the private key stays with its owner.
|
|
119
|
+
*
|
|
120
|
+
* @returns
|
|
121
|
+
*/
|
|
122
|
+
static async generateKeyPair() {
|
|
123
|
+
return await Keys.generateSerializedPair();
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Stretch a user supplied password into a key with PBKDF2-HMAC-SHA256.
|
|
127
|
+
*
|
|
128
|
+
* @param password
|
|
129
|
+
* @param options
|
|
130
|
+
* @returns
|
|
131
|
+
*/
|
|
132
|
+
static async deriveKey(password, options = {}) {
|
|
133
|
+
return await Keys.derive(password, options);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Constant time comparison of two keys.
|
|
137
|
+
*
|
|
138
|
+
* @param left
|
|
139
|
+
* @param right
|
|
140
|
+
* @returns
|
|
141
|
+
*/
|
|
142
|
+
static compareKeys(left, right) {
|
|
143
|
+
return Keys.matches(left, right);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* A displayable digest of a key, safe to show to users or write to logs.
|
|
147
|
+
*
|
|
148
|
+
* @param key Defaults to the application key.
|
|
149
|
+
* @param options
|
|
150
|
+
* @returns
|
|
151
|
+
*/
|
|
152
|
+
static async fingerprint(key, options = {}) {
|
|
153
|
+
return await Keys.fingerprint(key ?? this.material(), options);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Open an end-to-end encrypted channel between a local private key and a
|
|
157
|
+
* peer's public key. Neither key, nor the secret they agree on, ever
|
|
158
|
+
* crosses the wire.
|
|
159
|
+
*
|
|
160
|
+
* @param privateKey
|
|
161
|
+
* @param peerPublicKey
|
|
162
|
+
* @param options
|
|
163
|
+
* @returns
|
|
164
|
+
*/
|
|
165
|
+
static async channel(privateKey, peerPublicKey, options = {}) {
|
|
166
|
+
return await SecureChannel.between(privateKey, peerPublicKey, options);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Encrypt a message to a public key without needing a sender identity.
|
|
170
|
+
*
|
|
171
|
+
* @param message
|
|
172
|
+
* @param recipientPublicKey
|
|
173
|
+
* @returns
|
|
174
|
+
*/
|
|
175
|
+
static async seal(message, recipientPublicKey) {
|
|
176
|
+
return await SealedBox.seal(message, recipientPublicKey);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Open a payload produced by {@link seal}.
|
|
180
|
+
*
|
|
181
|
+
* @param payload
|
|
182
|
+
* @param recipientPrivateKey
|
|
183
|
+
* @returns
|
|
184
|
+
*/
|
|
185
|
+
static async open(payload, recipientPrivateKey) {
|
|
186
|
+
return await SealedBox.open(payload, recipientPrivateKey);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* The safety number for a conversation between two public keys — show it to
|
|
190
|
+
* both participants so they can verify nobody swapped a key in transit.
|
|
191
|
+
*
|
|
192
|
+
* @param first
|
|
193
|
+
* @param second
|
|
194
|
+
* @param groups
|
|
195
|
+
* @returns
|
|
196
|
+
*/
|
|
197
|
+
static async safetyNumber(first, second, groups = 12) {
|
|
198
|
+
return await Keys.safetyNumber(first, second, groups);
|
|
32
199
|
}
|
|
33
200
|
};
|
|
34
201
|
//#endregion
|
|
@@ -202,37 +369,11 @@ const resolvePagination = (query, defaults) => {
|
|
|
202
369
|
perPage: perPage(query, defaults)
|
|
203
370
|
};
|
|
204
371
|
};
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (!isModelModule(module)) return module;
|
|
208
|
-
return module.default ?? module[modelName] ?? module;
|
|
209
|
-
};
|
|
210
|
-
const isModelModule = (value) => typeof value === "object" && value !== null;
|
|
211
|
-
const { getUserConfig } = await import("arkormx");
|
|
212
|
-
const modelPath = getUserConfig().paths?.models || "./src/models";
|
|
213
|
-
const sourcePath = path.join(path.isAbsolute(modelPath) ? modelPath : path.join(Arkstack.rootDir(), modelPath), modelName);
|
|
214
|
-
const modulePath = resolveRuntimeModule(sourcePath);
|
|
215
|
-
const model = resolveModelExport(await importFile(modulePath), path.basename(modelName, path.extname(modelName)));
|
|
216
|
-
if (typeof model !== "function") throw new Error(`Model "${modelName}" not found`);
|
|
217
|
-
return model;
|
|
372
|
+
function getModel$1(modelName) {
|
|
373
|
+
return getModel(modelName);
|
|
218
374
|
}
|
|
219
|
-
const isModelModule = (value) => typeof value === "object" && value !== null;
|
|
220
|
-
const resolveModelExport = (module, modelName) => {
|
|
221
|
-
if (!isModelModule(module)) return module;
|
|
222
|
-
return module.default ?? module[modelName] ?? module;
|
|
223
|
-
};
|
|
224
375
|
function getModelSync(modelName) {
|
|
225
|
-
|
|
226
|
-
const { Arkorm, getUserConfig } = require("arkormx");
|
|
227
|
-
const exportName = path.basename(modelName, path.extname(modelName));
|
|
228
|
-
const registeredModel = Arkorm.getRegisteredModels().find((model) => model.name === exportName);
|
|
229
|
-
if (registeredModel) return registeredModel;
|
|
230
|
-
const modelPath = getUserConfig().paths?.models || "./src/models";
|
|
231
|
-
const sourcePath = path.join(path.isAbsolute(modelPath) ? modelPath : path.join(Arkstack.rootDir(), modelPath), modelName);
|
|
232
|
-
const module = require(resolveRuntimeModule(sourcePath));
|
|
233
|
-
const model = resolveModelExport(module, exportName);
|
|
234
|
-
if (typeof model !== "function") throw new Error(`Model "${modelName}" not found`);
|
|
235
|
-
return model;
|
|
376
|
+
return getModel(modelName);
|
|
236
377
|
}
|
|
237
378
|
const initializeGlobalContext = async ({ Request, Response, Session } = {}) => {
|
|
238
379
|
try {
|
|
@@ -492,4 +633,4 @@ function uses(instance, trait) {
|
|
|
492
633
|
return false;
|
|
493
634
|
}
|
|
494
635
|
//#endregion
|
|
495
|
-
export { RequestException as _, use as a, Hash as b, abortIf as c, getModelSync as d, initializeGlobalContext as f, resolvePagination as g, perPage as h, trait as i, assertFound as l, normalizePositiveInteger as m, crc32 as n, uses as o, isClass as p, getTraitMethods as r, abort as s, callTraitMethods as t, getModel as u, AppException as v,
|
|
636
|
+
export { Encryption as C, NodeCipher as D, Keys as E, SealedBox as O, Codec as S, KeyPair as T, RequestException as _, use as a, Hash as b, abortIf as c, getModelSync as d, initializeGlobalContext as f, resolvePagination as g, perPage as h, trait as i, SecureChannel as k, assertFound as l, normalizePositiveInteger as m, crc32 as n, uses as o, isClass as p, getTraitMethods as r, abort as s, callTraitMethods as t, getModel$1 as u, AppException as v, EncryptionKey as w, Cipher as x, Exception as y };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arkstack/common",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Core utilities, primitives, and shared infrastructure for the Arkstack ecosystem.",
|
|
6
6
|
"homepage": "https://arkstack.toneflix.net",
|
|
@@ -42,13 +42,14 @@
|
|
|
42
42
|
"jiti": "^2.7.0",
|
|
43
43
|
"otpauth": "^9.5.1",
|
|
44
44
|
"pino": "^10.3.1",
|
|
45
|
-
"selfsigned": "^2.4.1"
|
|
45
|
+
"selfsigned": "^2.4.1",
|
|
46
|
+
"@arkstack/encryption": "^0.18.0"
|
|
46
47
|
},
|
|
47
48
|
"peerDependencies": {
|
|
48
49
|
"@h3ravel/support": "^2.2.7",
|
|
49
50
|
"arkormx": "^2.12.8",
|
|
50
|
-
"@arkstack/contract": "^0.
|
|
51
|
-
"@arkstack/foundry": "^0.
|
|
51
|
+
"@arkstack/contract": "^0.18.0",
|
|
52
|
+
"@arkstack/foundry": "^0.18.0"
|
|
52
53
|
},
|
|
53
54
|
"optionalDependencies": {
|
|
54
55
|
"@faker-js/faker": "^10.4.0"
|