@twin.org/web 0.0.1-next.9 → 0.0.2-next.10
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/dist/cjs/index.cjs +311 -98
- package/dist/esm/index.mjs +312 -100
- package/dist/types/errors/fetchError.d.ts +2 -2
- package/dist/types/index.d.ts +3 -1
- package/dist/types/models/IJwk.d.ts +2 -58
- package/dist/types/models/IJwtHeader.d.ts +2 -18
- package/dist/types/models/IJwtPayload.d.ts +2 -33
- package/dist/types/models/jwkCryptoKey.d.ts +4 -0
- package/dist/types/models/mimeTypes.d.ts +8 -0
- package/dist/types/utils/fetchHelper.d.ts +7 -0
- package/dist/types/utils/jwk.d.ts +41 -0
- package/dist/types/utils/jws.d.ts +22 -0
- package/dist/types/utils/jwt.d.ts +67 -29
- package/docs/changelog.md +613 -1
- package/docs/reference/classes/FetchError.md +17 -9
- package/docs/reference/classes/FetchHelper.md +104 -28
- package/docs/reference/classes/Jwk.md +129 -0
- package/docs/reference/classes/Jws.md +81 -0
- package/docs/reference/classes/Jwt.md +261 -105
- package/docs/reference/classes/MimeTypeHelper.md +9 -5
- package/docs/reference/index.md +3 -2
- package/docs/reference/interfaces/IHttpHeaders.md +1 -1
- package/docs/reference/interfaces/IJwk.md +2 -106
- package/docs/reference/interfaces/IJwtHeader.md +5 -23
- package/docs/reference/interfaces/IJwtPayload.md +5 -55
- package/docs/reference/type-aliases/HeaderTypes.md +1 -1
- package/docs/reference/type-aliases/HttpMethod.md +1 -1
- package/docs/reference/type-aliases/HttpStatusCode.md +1 -1
- package/docs/reference/type-aliases/JwkCryptoKey.md +5 -0
- package/docs/reference/type-aliases/MimeTypes.md +1 -1
- package/docs/reference/variables/HeaderTypes.md +1 -1
- package/docs/reference/variables/HttpMethod.md +1 -1
- package/docs/reference/variables/HttpStatusCode.md +1 -1
- package/docs/reference/variables/MimeTypes.md +13 -1
- package/locales/en.json +11 -1
- package/package.json +7 -6
- package/dist/types/models/jwtAlgorithms.d.ts +0 -17
- package/docs/reference/type-aliases/JwtAlgorithms.md +0 -5
- package/docs/reference/variables/JwtAlgorithms.md +0 -19
package/dist/esm/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { BaseError, StringHelper, Guards, Is, AsyncCache, ObjectHelper,
|
|
2
|
-
import {
|
|
1
|
+
import { BaseError, StringHelper, Guards, Is, AsyncCache, ObjectHelper, GeneralError, Converter, JsonHelper } from '@twin.org/core';
|
|
2
|
+
import { Ed25519, Sha256 } from '@twin.org/crypto';
|
|
3
|
+
import { importJWK, CompactSign, flattenedVerify, SignJWT, jwtVerify } from 'jose';
|
|
3
4
|
|
|
4
5
|
// Copyright 2024 IOTA Stiftung.
|
|
5
6
|
// SPDX-License-Identifier: Apache-2.0.
|
|
@@ -17,13 +18,13 @@ class FetchError extends BaseError {
|
|
|
17
18
|
* @param message The message as a code.
|
|
18
19
|
* @param httpStatus The http status code.
|
|
19
20
|
* @param properties Any additional information for the error.
|
|
20
|
-
* @param
|
|
21
|
+
* @param cause The cause of the error if we have wrapped another error.
|
|
21
22
|
*/
|
|
22
|
-
constructor(source, message, httpStatus, properties,
|
|
23
|
+
constructor(source, message, httpStatus, properties, cause) {
|
|
23
24
|
super(FetchError.CLASS_NAME, source, message, {
|
|
24
25
|
httpStatus,
|
|
25
26
|
...properties
|
|
26
|
-
},
|
|
27
|
+
}, cause);
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
|
|
@@ -343,23 +344,6 @@ const HttpStatusCode = {
|
|
|
343
344
|
networkAuthenticationRequired: 511
|
|
344
345
|
};
|
|
345
346
|
|
|
346
|
-
// Copyright 2024 IOTA Stiftung.
|
|
347
|
-
// SPDX-License-Identifier: Apache-2.0.
|
|
348
|
-
/**
|
|
349
|
-
* The cryptographic algorithms supported for JSON Web Tokens and JSON Web Keys.
|
|
350
|
-
*/
|
|
351
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
352
|
-
const JwtAlgorithms = {
|
|
353
|
-
/**
|
|
354
|
-
* HMAC using SHA-256.
|
|
355
|
-
*/
|
|
356
|
-
HS256: "HS256",
|
|
357
|
-
/**
|
|
358
|
-
* EdDSA using Ed25519.
|
|
359
|
-
*/
|
|
360
|
-
EdDSA: "EdDSA"
|
|
361
|
-
};
|
|
362
|
-
|
|
363
347
|
// Copyright 2024 IOTA Stiftung.
|
|
364
348
|
// SPDX-License-Identifier: Apache-2.0.
|
|
365
349
|
/**
|
|
@@ -387,6 +371,10 @@ const MimeTypes = {
|
|
|
387
371
|
* JSON-LD - application/ld+json
|
|
388
372
|
*/
|
|
389
373
|
JsonLd: "application/ld+json",
|
|
374
|
+
/**
|
|
375
|
+
* JWT - application/jwt
|
|
376
|
+
*/
|
|
377
|
+
Jwt: "application/jwt",
|
|
390
378
|
/**
|
|
391
379
|
* XML - application/xml
|
|
392
380
|
*/
|
|
@@ -399,6 +387,10 @@ const MimeTypes = {
|
|
|
399
387
|
* Application GZIP - application/gzip
|
|
400
388
|
*/
|
|
401
389
|
Gzip: "application/gzip",
|
|
390
|
+
/**
|
|
391
|
+
* Application deflate - application/zlib
|
|
392
|
+
*/
|
|
393
|
+
Zlib: "application/zlib",
|
|
402
394
|
/**
|
|
403
395
|
* Application BZIP2 - application/x-bzip2
|
|
404
396
|
*/
|
|
@@ -527,11 +519,20 @@ class FetchHelper {
|
|
|
527
519
|
}
|
|
528
520
|
}, options?.timeoutMs);
|
|
529
521
|
}
|
|
522
|
+
let finalBody;
|
|
523
|
+
if (method === HttpMethod.POST || method === HttpMethod.PUT) {
|
|
524
|
+
if (Is.string(body)) {
|
|
525
|
+
finalBody = body;
|
|
526
|
+
}
|
|
527
|
+
else if (Is.uint8Array(body)) {
|
|
528
|
+
finalBody = new Uint8Array(body);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
530
531
|
try {
|
|
531
532
|
const requestOptions = {
|
|
532
533
|
method,
|
|
533
534
|
headers: options?.headers,
|
|
534
|
-
body:
|
|
535
|
+
body: finalBody,
|
|
535
536
|
signal: controller ? controller.signal : undefined
|
|
536
537
|
};
|
|
537
538
|
if (Is.boolean(options?.includeCredentials)) {
|
|
@@ -553,7 +554,7 @@ class FetchHelper {
|
|
|
553
554
|
if (isErr && Is.stringValue(err.message) && err.message.includes("Failed to fetch")) {
|
|
554
555
|
lastError = new FetchError(source, `${FetchHelper._CLASS_NAME_CAMEL_CASE}.connectivity`, HttpStatusCode.serviceUnavailable, {
|
|
555
556
|
url
|
|
556
|
-
});
|
|
557
|
+
}, err);
|
|
557
558
|
}
|
|
558
559
|
else {
|
|
559
560
|
const isAbort = isErr && err.name === "AbortError";
|
|
@@ -568,7 +569,7 @@ class FetchHelper {
|
|
|
568
569
|
if (isErr && "statusText" in err) {
|
|
569
570
|
props.statusText = err.statusText;
|
|
570
571
|
}
|
|
571
|
-
lastError = new FetchError(source, `${FetchHelper._CLASS_NAME_CAMEL_CASE}.${isAbort ? "timeout" : "general"}`, httpStatus, props);
|
|
572
|
+
lastError = new FetchError(source, `${FetchHelper._CLASS_NAME_CAMEL_CASE}.${isAbort ? "timeout" : "general"}`, httpStatus, props, err);
|
|
572
573
|
}
|
|
573
574
|
}
|
|
574
575
|
finally {
|
|
@@ -625,12 +626,15 @@ class FetchHelper {
|
|
|
625
626
|
}
|
|
626
627
|
}
|
|
627
628
|
const errorResponseData = await response.json();
|
|
629
|
+
const errorResponse = BaseError.fromError(errorResponseData);
|
|
630
|
+
const isErrorEmpty = BaseError.isEmpty(errorResponse);
|
|
628
631
|
// False positive as FetchError is derived from Error
|
|
629
632
|
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
630
633
|
throw new FetchError(source, `${FetchHelper._CLASS_NAME_CAMEL_CASE}.failureStatusText`, response.status, {
|
|
631
634
|
statusText: response.statusText,
|
|
632
|
-
url
|
|
633
|
-
|
|
635
|
+
url,
|
|
636
|
+
data: isErrorEmpty ? errorResponseData : undefined
|
|
637
|
+
}, isErrorEmpty ? undefined : errorResponse);
|
|
634
638
|
}
|
|
635
639
|
/**
|
|
636
640
|
* Perform a request for binary data.
|
|
@@ -675,12 +679,15 @@ class FetchHelper {
|
|
|
675
679
|
}
|
|
676
680
|
}
|
|
677
681
|
const errorResponseData = await response.json();
|
|
682
|
+
const errorResponse = BaseError.fromError(errorResponseData);
|
|
683
|
+
const isErrorEmpty = BaseError.isEmpty(errorResponse);
|
|
678
684
|
// False positive as FetchError is derived from Error
|
|
679
685
|
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
680
686
|
throw new FetchError(source, `${FetchHelper._CLASS_NAME_CAMEL_CASE}.failureStatusText`, response.status, {
|
|
681
687
|
statusText: response.statusText,
|
|
682
|
-
url
|
|
683
|
-
|
|
688
|
+
url,
|
|
689
|
+
data: isErrorEmpty ? errorResponseData : undefined
|
|
690
|
+
}, isErrorEmpty ? undefined : errorResponse);
|
|
684
691
|
}
|
|
685
692
|
/**
|
|
686
693
|
* Clears the cache.
|
|
@@ -696,6 +703,15 @@ class FetchHelper {
|
|
|
696
703
|
static async getCacheEntry(url) {
|
|
697
704
|
return AsyncCache.get(`${FetchHelper._CACHE_PREFIX}${url}`);
|
|
698
705
|
}
|
|
706
|
+
/**
|
|
707
|
+
* Set a cache entry.
|
|
708
|
+
* @param url The url for the request.
|
|
709
|
+
* @param value The value to cache.
|
|
710
|
+
* @returns The cache entry if it exists.
|
|
711
|
+
*/
|
|
712
|
+
static async setCacheEntry(url, value) {
|
|
713
|
+
AsyncCache.set(`${FetchHelper._CACHE_PREFIX}${url}`, value);
|
|
714
|
+
}
|
|
699
715
|
/**
|
|
700
716
|
* Remove a cache entry.
|
|
701
717
|
* @param url The url for the request.
|
|
@@ -708,7 +724,158 @@ class FetchHelper {
|
|
|
708
724
|
// Copyright 2024 IOTA Stiftung.
|
|
709
725
|
// SPDX-License-Identifier: Apache-2.0.
|
|
710
726
|
/**
|
|
711
|
-
* Class to
|
|
727
|
+
* Class to handle JSON Web Keys.
|
|
728
|
+
*/
|
|
729
|
+
class Jwk {
|
|
730
|
+
/**
|
|
731
|
+
* Runtime name for the class.
|
|
732
|
+
* @internal
|
|
733
|
+
*/
|
|
734
|
+
static _CLASS_NAME = "Jwk";
|
|
735
|
+
/**
|
|
736
|
+
* Convert the JWK to a crypto key.
|
|
737
|
+
* @param jwk The JWK to convert.
|
|
738
|
+
* @param alg The alg to be used, defaults to jwk.alg.
|
|
739
|
+
* @returns The crypto key.
|
|
740
|
+
*/
|
|
741
|
+
static async toCryptoKey(jwk, alg) {
|
|
742
|
+
Guards.object(Jwk._CLASS_NAME, "jwk", jwk);
|
|
743
|
+
try {
|
|
744
|
+
return importJWK(jwk, alg);
|
|
745
|
+
}
|
|
746
|
+
catch (err) {
|
|
747
|
+
throw new GeneralError(Jwk._CLASS_NAME, "jwkImportFailed", undefined, err);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Convert the Ed25519 private key to a crypto key.
|
|
752
|
+
* @param privateKey The private key to use.
|
|
753
|
+
* @returns The crypto key.
|
|
754
|
+
*/
|
|
755
|
+
static async fromEd25519Private(privateKey) {
|
|
756
|
+
Guards.uint8Array(Jwk._CLASS_NAME, "privateKey", privateKey);
|
|
757
|
+
const publicKey = Ed25519.publicKeyFromPrivateKey(privateKey);
|
|
758
|
+
const jwk = {
|
|
759
|
+
kty: "OKP",
|
|
760
|
+
use: "enc",
|
|
761
|
+
alg: "EdDSA",
|
|
762
|
+
crv: "Ed25519",
|
|
763
|
+
x: Converter.bytesToBase64Url(publicKey),
|
|
764
|
+
d: Converter.bytesToBase64Url(privateKey)
|
|
765
|
+
};
|
|
766
|
+
return jwk;
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Convert the Ed25519 public key to a crypto key.
|
|
770
|
+
* @param publicKey The private key to use.
|
|
771
|
+
* @returns The crypto key.
|
|
772
|
+
*/
|
|
773
|
+
static async fromEd25519Public(publicKey) {
|
|
774
|
+
Guards.uint8Array(Jwk._CLASS_NAME, "publicKey", publicKey);
|
|
775
|
+
const jwk = {
|
|
776
|
+
kty: "OKP",
|
|
777
|
+
use: "sig",
|
|
778
|
+
alg: "EdDSA",
|
|
779
|
+
crv: "Ed25519",
|
|
780
|
+
x: Converter.bytesToBase64Url(publicKey)
|
|
781
|
+
};
|
|
782
|
+
return jwk;
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Convert the JWK to raw keys.
|
|
786
|
+
* @param jwk The JWK to convert to raw.
|
|
787
|
+
* @returns The crypto key.
|
|
788
|
+
*/
|
|
789
|
+
static async toRaw(jwk) {
|
|
790
|
+
Guards.object(Jwk._CLASS_NAME, "jwk", jwk);
|
|
791
|
+
let publicKey;
|
|
792
|
+
let privateKey;
|
|
793
|
+
if (Is.stringBase64Url(jwk.x)) {
|
|
794
|
+
publicKey = Converter.base64UrlToBytes(jwk.x);
|
|
795
|
+
}
|
|
796
|
+
if (Is.stringBase64Url(jwk.d)) {
|
|
797
|
+
privateKey = Converter.base64UrlToBytes(jwk.d);
|
|
798
|
+
}
|
|
799
|
+
return {
|
|
800
|
+
publicKey,
|
|
801
|
+
privateKey
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Generate a KID for the JWK.
|
|
806
|
+
* @param jwk The JWK to generate a KID for.
|
|
807
|
+
* @returns The KID.
|
|
808
|
+
*/
|
|
809
|
+
static async generateKid(jwk) {
|
|
810
|
+
Guards.object(Jwk._CLASS_NAME, "jwk", jwk);
|
|
811
|
+
const kidProps = ObjectHelper.pick(jwk, ["crv", "kty", "x"]);
|
|
812
|
+
const canonicalJson = JsonHelper.canonicalize(kidProps);
|
|
813
|
+
const hash = Sha256.sum256(Converter.utf8ToBytes(canonicalJson));
|
|
814
|
+
return Converter.bytesToBase64Url(hash);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// Copyright 2024 IOTA Stiftung.
|
|
819
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
820
|
+
/**
|
|
821
|
+
* Class to handle JSON Web Signatures.
|
|
822
|
+
*/
|
|
823
|
+
class Jws {
|
|
824
|
+
/**
|
|
825
|
+
* Runtime name for the class.
|
|
826
|
+
* @internal
|
|
827
|
+
*/
|
|
828
|
+
static _CLASS_NAME = "Jws";
|
|
829
|
+
/**
|
|
830
|
+
* Create a signature.
|
|
831
|
+
* @param privateKey The private key to use.
|
|
832
|
+
* @param hash The hash to sign.
|
|
833
|
+
* @param algOverride An optional algorithm override.
|
|
834
|
+
* @returns The signature.
|
|
835
|
+
*/
|
|
836
|
+
static async create(privateKey, hash, algOverride) {
|
|
837
|
+
Guards.defined(Jws._CLASS_NAME, "privateKey", privateKey);
|
|
838
|
+
Guards.uint8Array(Jws._CLASS_NAME, "hash", hash);
|
|
839
|
+
try {
|
|
840
|
+
const jws = await new CompactSign(hash)
|
|
841
|
+
.setProtectedHeader({
|
|
842
|
+
alg: algOverride ?? (Is.uint8Array(privateKey) ? "EdDSA" : privateKey.algorithm.name),
|
|
843
|
+
b64: false,
|
|
844
|
+
crit: ["b64"]
|
|
845
|
+
})
|
|
846
|
+
.sign(privateKey);
|
|
847
|
+
return jws;
|
|
848
|
+
}
|
|
849
|
+
catch (err) {
|
|
850
|
+
throw new GeneralError(Jws._CLASS_NAME, "createFailed", undefined, err);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Verify a signature.
|
|
855
|
+
* @param jws The signature to verify.
|
|
856
|
+
* @param publicKey The public key to verify the signature with.
|
|
857
|
+
* @param hash The hash to verify.
|
|
858
|
+
* @returns True if the signature was verified.
|
|
859
|
+
*/
|
|
860
|
+
static async verify(jws, publicKey, hash) {
|
|
861
|
+
Guards.stringValue(Jws._CLASS_NAME, "jws", jws);
|
|
862
|
+
Guards.defined(Jws._CLASS_NAME, "publicKey", publicKey);
|
|
863
|
+
Guards.uint8Array(Jws._CLASS_NAME, "hash", hash);
|
|
864
|
+
try {
|
|
865
|
+
const jwsParts = jws.split(".");
|
|
866
|
+
await flattenedVerify({ protected: jwsParts[0], payload: hash, signature: jwsParts[2] }, publicKey);
|
|
867
|
+
return true;
|
|
868
|
+
}
|
|
869
|
+
catch (err) {
|
|
870
|
+
throw new GeneralError(Jws._CLASS_NAME, "verifyFailed", undefined, err);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// Copyright 2024 IOTA Stiftung.
|
|
876
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
877
|
+
/**
|
|
878
|
+
* Class to handle JSON Web Tokens.
|
|
712
879
|
*/
|
|
713
880
|
class Jwt {
|
|
714
881
|
/**
|
|
@@ -725,9 +892,8 @@ class Jwt {
|
|
|
725
892
|
*/
|
|
726
893
|
static async encode(header, payload, key) {
|
|
727
894
|
Guards.object(Jwt._CLASS_NAME, "header", header);
|
|
728
|
-
Guards.arrayOneOf(Jwt._CLASS_NAME, "header.alg", header.alg, Object.values(JwtAlgorithms));
|
|
729
895
|
Guards.object(Jwt._CLASS_NAME, "payload", payload);
|
|
730
|
-
Guards.
|
|
896
|
+
Guards.defined(Jwt._CLASS_NAME, "key", key);
|
|
731
897
|
return Jwt.internalEncode(header, payload, key);
|
|
732
898
|
}
|
|
733
899
|
/**
|
|
@@ -739,7 +905,7 @@ class Jwt {
|
|
|
739
905
|
*/
|
|
740
906
|
static async encodeWithSigner(header, payload, signer) {
|
|
741
907
|
Guards.object(Jwt._CLASS_NAME, "header", header);
|
|
742
|
-
Guards.
|
|
908
|
+
Guards.stringValue(Jwt._CLASS_NAME, "header.alg", header.alg);
|
|
743
909
|
Guards.object(Jwt._CLASS_NAME, "payload", payload);
|
|
744
910
|
Guards.function(Jwt._CLASS_NAME, "signer", signer);
|
|
745
911
|
return Jwt.internalEncode(header, payload, undefined, signer);
|
|
@@ -786,13 +952,8 @@ class Jwt {
|
|
|
786
952
|
*/
|
|
787
953
|
static async verify(token, key) {
|
|
788
954
|
Guards.stringValue(Jwt._CLASS_NAME, "token", token);
|
|
789
|
-
Guards.
|
|
790
|
-
|
|
791
|
-
const verified = await Jwt.verifySignature(decoded.header, decoded.payload, decoded.signature, key);
|
|
792
|
-
return {
|
|
793
|
-
verified,
|
|
794
|
-
...decoded
|
|
795
|
-
};
|
|
955
|
+
Guards.defined(Jwt._CLASS_NAME, "key", key);
|
|
956
|
+
return Jwt.verifySignature(token, key);
|
|
796
957
|
}
|
|
797
958
|
/**
|
|
798
959
|
* Verify a token.
|
|
@@ -803,79 +964,131 @@ class Jwt {
|
|
|
803
964
|
static async verifyWithVerifier(token, verifier) {
|
|
804
965
|
Guards.stringValue(Jwt._CLASS_NAME, "token", token);
|
|
805
966
|
Guards.function(Jwt._CLASS_NAME, "verifier", verifier);
|
|
806
|
-
|
|
807
|
-
const decoded = await Jwt.decode(token);
|
|
808
|
-
const verified = await Jwt.verifySignature(decoded.header, decoded.payload, decoded.signature, undefined, verifier);
|
|
809
|
-
return {
|
|
810
|
-
verified,
|
|
811
|
-
...decoded
|
|
812
|
-
};
|
|
967
|
+
return Jwt.verifySignature(token, undefined, verifier);
|
|
813
968
|
}
|
|
814
969
|
/**
|
|
815
970
|
* Verify a token by parts.
|
|
816
|
-
* @param
|
|
817
|
-
* @param payload The payload to verify.
|
|
818
|
-
* @param signature The signature to verify.
|
|
971
|
+
* @param token The token to verify.
|
|
819
972
|
* @param key The key for verifying the token, if not provided no verification occurs.
|
|
820
973
|
* @param verifier Custom verification method.
|
|
821
974
|
* @returns True if the parts are verified.
|
|
822
975
|
*/
|
|
823
|
-
static async verifySignature(
|
|
976
|
+
static async verifySignature(token, key, verifier) {
|
|
977
|
+
Guards.stringValue(Jwt._CLASS_NAME, "token", token);
|
|
824
978
|
const hasKey = Is.notEmpty(key);
|
|
825
979
|
const hasVerifier = Is.notEmpty(verifier);
|
|
826
980
|
if (!hasKey && !hasVerifier) {
|
|
827
981
|
throw new GeneralError(Jwt._CLASS_NAME, "noKeyOrVerifier");
|
|
828
982
|
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
Is.object(payload) &&
|
|
832
|
-
Is.uint8Array(signature) &&
|
|
833
|
-
Is.arrayOneOf(header.alg, Object.values(JwtAlgorithms))) {
|
|
834
|
-
const segments = [];
|
|
835
|
-
const headerBytes = Converter.utf8ToBytes(JSON.stringify(header));
|
|
836
|
-
segments.push(Converter.bytesToBase64Url(headerBytes));
|
|
837
|
-
const payloadBytes = Converter.utf8ToBytes(JSON.stringify(payload));
|
|
838
|
-
segments.push(Converter.bytesToBase64Url(payloadBytes));
|
|
839
|
-
const jwtHeaderAndPayload = Converter.utf8ToBytes(segments.join("."));
|
|
840
|
-
verifier ??= async (alg, k, p, s) => Jwt.defaultVerifier(alg, k, p, s);
|
|
841
|
-
verified = await verifier(header.alg, key, jwtHeaderAndPayload, signature);
|
|
842
|
-
}
|
|
843
|
-
return verified;
|
|
983
|
+
verifier ??= async (t, k) => Jwt.defaultVerifier(t, k);
|
|
984
|
+
return verifier(token, key);
|
|
844
985
|
}
|
|
845
986
|
/**
|
|
846
987
|
* The default signer for the JWT.
|
|
847
|
-
* @param
|
|
848
|
-
* @param key The key to sign with.
|
|
988
|
+
* @param header The header to sign.
|
|
849
989
|
* @param payload The payload to sign.
|
|
990
|
+
* @param key The optional key to sign with.
|
|
850
991
|
* @returns The signature.
|
|
851
992
|
*/
|
|
852
|
-
static async defaultSigner(
|
|
853
|
-
Guards.
|
|
854
|
-
Guards.
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
993
|
+
static async defaultSigner(header, payload, key) {
|
|
994
|
+
Guards.object(Jwt._CLASS_NAME, "header", header);
|
|
995
|
+
Guards.object(Jwt._CLASS_NAME, "payload", payload);
|
|
996
|
+
Guards.defined(Jwt._CLASS_NAME, "key", key);
|
|
997
|
+
const signer = new SignJWT(payload);
|
|
998
|
+
signer.setProtectedHeader(header);
|
|
999
|
+
let finalKey = key;
|
|
1000
|
+
if (header.alg === "EdDSA" && Is.uint8Array(key)) {
|
|
1001
|
+
// Jose does not support Ed25519 keys in raw format, so we need to convert it to PKCS8.
|
|
1002
|
+
finalKey = await Ed25519.privateKeyToPkcs8(key);
|
|
858
1003
|
}
|
|
859
|
-
return
|
|
1004
|
+
return signer.sign(finalKey);
|
|
860
1005
|
}
|
|
861
1006
|
/**
|
|
862
1007
|
* The default verifier for the JWT.
|
|
863
|
-
* @param
|
|
1008
|
+
* @param token The token to verify.
|
|
864
1009
|
* @param key The key to verify with.
|
|
865
|
-
* @
|
|
866
|
-
|
|
867
|
-
|
|
1010
|
+
* @returns The header and payload if verification successful.
|
|
1011
|
+
*/
|
|
1012
|
+
static async defaultVerifier(token, key) {
|
|
1013
|
+
Guards.stringValue(Jwt._CLASS_NAME, "token", token);
|
|
1014
|
+
Guards.defined(Jwt._CLASS_NAME, "key", key);
|
|
1015
|
+
try {
|
|
1016
|
+
const result = await jwtVerify(token, key);
|
|
1017
|
+
return {
|
|
1018
|
+
header: result.protectedHeader,
|
|
1019
|
+
payload: result.payload
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
catch (err) {
|
|
1023
|
+
throw new GeneralError(Jwt._CLASS_NAME, "verifyFailed", undefined, err);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Create bytes for signing from header and payload.
|
|
1028
|
+
* @param header The header.
|
|
1029
|
+
* @param payload The payload.
|
|
1030
|
+
* @returns The bytes to sign.
|
|
1031
|
+
*/
|
|
1032
|
+
static toSigningBytes(header, payload) {
|
|
1033
|
+
Guards.object(Jwt._CLASS_NAME, "header", header);
|
|
1034
|
+
Guards.object(Jwt._CLASS_NAME, "payload", payload);
|
|
1035
|
+
const segments = [];
|
|
1036
|
+
const headerBytes = Converter.utf8ToBytes(JSON.stringify(header));
|
|
1037
|
+
segments.push(Converter.bytesToBase64Url(headerBytes));
|
|
1038
|
+
const payloadBytes = Converter.utf8ToBytes(JSON.stringify(payload));
|
|
1039
|
+
segments.push(Converter.bytesToBase64Url(payloadBytes));
|
|
1040
|
+
return Converter.utf8ToBytes(segments.join("."));
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Create header and payload from signing bytes.
|
|
1044
|
+
* @param signingBytes The signing bytes from a token.
|
|
1045
|
+
* @returns The header and payload.
|
|
1046
|
+
* @throws If the signing bytes are invalid
|
|
868
1047
|
*/
|
|
869
|
-
static
|
|
870
|
-
Guards.uint8Array(Jwt._CLASS_NAME, "
|
|
871
|
-
|
|
1048
|
+
static fromSigningBytes(signingBytes) {
|
|
1049
|
+
Guards.uint8Array(Jwt._CLASS_NAME, "signingBytes", signingBytes);
|
|
1050
|
+
const segments = Converter.bytesToUtf8(signingBytes).split(".");
|
|
1051
|
+
if (segments.length !== 2) {
|
|
1052
|
+
throw new GeneralError(Jwt._CLASS_NAME, "invalidSigningBytes");
|
|
1053
|
+
}
|
|
1054
|
+
const headerBytes = Converter.base64UrlToBytes(segments[0]);
|
|
1055
|
+
const payloadBytes = Converter.base64UrlToBytes(segments[1]);
|
|
1056
|
+
return {
|
|
1057
|
+
header: ObjectHelper.fromBytes(headerBytes),
|
|
1058
|
+
payload: ObjectHelper.fromBytes(payloadBytes)
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Convert signed bytes and signature bytes to token.
|
|
1063
|
+
* @param signingBytes The signed bytes.
|
|
1064
|
+
* @param signature The signature.
|
|
1065
|
+
* @returns The token.
|
|
1066
|
+
*/
|
|
1067
|
+
static tokenFromBytes(signingBytes, signature) {
|
|
1068
|
+
Guards.uint8Array(Jwt._CLASS_NAME, "signingBytes", signingBytes);
|
|
872
1069
|
Guards.uint8Array(Jwt._CLASS_NAME, "signature", signature);
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
1070
|
+
const signedBytesUtf8 = Converter.bytesToUtf8(signingBytes);
|
|
1071
|
+
const signatureBase64 = Converter.bytesToBase64Url(signature);
|
|
1072
|
+
return `${signedBytesUtf8}.${signatureBase64}`;
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Convert the token to signing bytes and signature bytes.
|
|
1076
|
+
* @param token The token to convert to bytes.
|
|
1077
|
+
* @returns The decoded bytes.
|
|
1078
|
+
* @throws If the token is invalid.
|
|
1079
|
+
*/
|
|
1080
|
+
static tokenToBytes(token) {
|
|
1081
|
+
Guards.stringValue(Jwt._CLASS_NAME, "token", token);
|
|
1082
|
+
const segments = token.split(".");
|
|
1083
|
+
if (segments.length !== 3) {
|
|
1084
|
+
throw new GeneralError(Jwt._CLASS_NAME, "invalidTokenParts");
|
|
877
1085
|
}
|
|
878
|
-
|
|
1086
|
+
const signingBytes = Converter.utf8ToBytes(`${segments[0]}.${segments[1]}`);
|
|
1087
|
+
const signature = Converter.base64UrlToBytes(segments[2]);
|
|
1088
|
+
return {
|
|
1089
|
+
signingBytes,
|
|
1090
|
+
signature
|
|
1091
|
+
};
|
|
879
1092
|
}
|
|
880
1093
|
/**
|
|
881
1094
|
* Encode a token.
|
|
@@ -892,19 +1105,11 @@ class Jwt {
|
|
|
892
1105
|
if (!hasKey && !hasSigner) {
|
|
893
1106
|
throw new GeneralError(Jwt._CLASS_NAME, "noKeyOrSigner");
|
|
894
1107
|
}
|
|
895
|
-
signer ??= async (
|
|
1108
|
+
signer ??= async (h, p, k) => Jwt.defaultSigner(h, p, k);
|
|
896
1109
|
if (Is.undefined(header.typ)) {
|
|
897
1110
|
header.typ = "JWT";
|
|
898
1111
|
}
|
|
899
|
-
|
|
900
|
-
const headerBytes = Converter.utf8ToBytes(JSON.stringify(header));
|
|
901
|
-
segments.push(Converter.bytesToBase64Url(headerBytes));
|
|
902
|
-
const payloadBytes = Converter.utf8ToBytes(JSON.stringify(payload));
|
|
903
|
-
segments.push(Converter.bytesToBase64Url(payloadBytes));
|
|
904
|
-
const jwtHeaderAndPayload = Converter.utf8ToBytes(segments.join("."));
|
|
905
|
-
const sigBytes = await signer(header.alg, key, jwtHeaderAndPayload);
|
|
906
|
-
segments.push(Converter.bytesToBase64Url(sigBytes));
|
|
907
|
-
return segments.join(".");
|
|
1112
|
+
return signer(header, payload, key);
|
|
908
1113
|
}
|
|
909
1114
|
}
|
|
910
1115
|
|
|
@@ -920,7 +1125,7 @@ class MimeTypeHelper {
|
|
|
920
1125
|
* @returns The mime type if detected.
|
|
921
1126
|
*/
|
|
922
1127
|
static async detect(data) {
|
|
923
|
-
if (!Is.uint8Array(data)) {
|
|
1128
|
+
if (!Is.uint8Array(data) || data.length === 0) {
|
|
924
1129
|
return undefined;
|
|
925
1130
|
}
|
|
926
1131
|
// Image
|
|
@@ -944,6 +1149,11 @@ class MimeTypeHelper {
|
|
|
944
1149
|
if (MimeTypeHelper.checkBytes(data, [0x1f, 0x8b, 0x8])) {
|
|
945
1150
|
return MimeTypes.Gzip;
|
|
946
1151
|
}
|
|
1152
|
+
if (MimeTypeHelper.checkBytes(data, [0x78, 0x01]) ||
|
|
1153
|
+
MimeTypeHelper.checkBytes(data, [0x78, 0x9c]) ||
|
|
1154
|
+
MimeTypeHelper.checkBytes(data, [0x78, 0xda])) {
|
|
1155
|
+
return MimeTypes.Zlib;
|
|
1156
|
+
}
|
|
947
1157
|
if (MimeTypeHelper.checkBytes(data, [0x42, 0x5a, 0x68])) {
|
|
948
1158
|
return MimeTypes.Bzip2;
|
|
949
1159
|
}
|
|
@@ -992,12 +1202,14 @@ class MimeTypeHelper {
|
|
|
992
1202
|
[MimeTypes.Javascript]: "js",
|
|
993
1203
|
[MimeTypes.Json]: "json",
|
|
994
1204
|
[MimeTypes.JsonLd]: "jsonld",
|
|
1205
|
+
[MimeTypes.Jwt]: "jwt",
|
|
995
1206
|
[MimeTypes.Xml]: "xml",
|
|
996
1207
|
[MimeTypes.OctetStream]: "bin",
|
|
997
1208
|
[MimeTypes.Gzip]: "gzip",
|
|
1209
|
+
[MimeTypes.Zlib]: "zlib",
|
|
998
1210
|
[MimeTypes.Bzip2]: "bz2",
|
|
999
1211
|
[MimeTypes.Zip]: "zip",
|
|
1000
|
-
[MimeTypes.Pdf]: "
|
|
1212
|
+
[MimeTypes.Pdf]: "pdf",
|
|
1001
1213
|
[MimeTypes.Gif]: "gif",
|
|
1002
1214
|
[MimeTypes.Bmp]: "bmp",
|
|
1003
1215
|
[MimeTypes.Jpeg]: "jpeg",
|
|
@@ -1043,4 +1255,4 @@ class MimeTypeHelper {
|
|
|
1043
1255
|
}
|
|
1044
1256
|
}
|
|
1045
1257
|
|
|
1046
|
-
export { FetchError, FetchHelper, HeaderTypes, HttpMethod, HttpStatusCode,
|
|
1258
|
+
export { FetchError, FetchHelper, HeaderTypes, HttpMethod, HttpStatusCode, Jwk, Jws, Jwt, MimeTypeHelper, MimeTypes };
|
|
@@ -14,9 +14,9 @@ export declare class FetchError extends BaseError {
|
|
|
14
14
|
* @param message The message as a code.
|
|
15
15
|
* @param httpStatus The http status code.
|
|
16
16
|
* @param properties Any additional information for the error.
|
|
17
|
-
* @param
|
|
17
|
+
* @param cause The cause of the error if we have wrapped another error.
|
|
18
18
|
*/
|
|
19
19
|
constructor(source: string, message: string, httpStatus: HttpStatusCode, properties?: {
|
|
20
20
|
[id: string]: unknown;
|
|
21
|
-
},
|
|
21
|
+
}, cause?: unknown);
|
|
22
22
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -7,8 +7,10 @@ export * from "./models/IHttpHeaders";
|
|
|
7
7
|
export * from "./models/IJwk";
|
|
8
8
|
export * from "./models/IJwtHeader";
|
|
9
9
|
export * from "./models/IJwtPayload";
|
|
10
|
-
export * from "./models/
|
|
10
|
+
export * from "./models/jwkCryptoKey";
|
|
11
11
|
export * from "./models/mimeTypes";
|
|
12
12
|
export * from "./utils/fetchHelper";
|
|
13
|
+
export * from "./utils/jwk";
|
|
14
|
+
export * from "./utils/jws";
|
|
13
15
|
export * from "./utils/jwt";
|
|
14
16
|
export * from "./utils/mimeTypeHelper";
|
|
@@ -1,62 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { JWK } from "jose";
|
|
2
2
|
/**
|
|
3
3
|
* The fields in a JSON Web Key.
|
|
4
4
|
*/
|
|
5
|
-
export interface IJwk {
|
|
6
|
-
/**
|
|
7
|
-
* Additional fields in the key.
|
|
8
|
-
*/
|
|
9
|
-
[key: string]: unknown;
|
|
10
|
-
/**
|
|
11
|
-
* The cryptographic algorithm for the key.
|
|
12
|
-
*/
|
|
13
|
-
alg?: JwtAlgorithms;
|
|
14
|
-
/**
|
|
15
|
-
* The intended use for the key.
|
|
16
|
-
*/
|
|
17
|
-
use?: string;
|
|
18
|
-
/**
|
|
19
|
-
* The operation(s) that the key is intended to be used for.
|
|
20
|
-
*/
|
|
21
|
-
key_ops?: string[];
|
|
22
|
-
/**
|
|
23
|
-
* The key type parameter.
|
|
24
|
-
*/
|
|
25
|
-
kty: string;
|
|
26
|
-
/**
|
|
27
|
-
* The public key parameters.
|
|
28
|
-
*/
|
|
29
|
-
n?: string;
|
|
30
|
-
/**
|
|
31
|
-
* Exponent parameter.
|
|
32
|
-
*/
|
|
33
|
-
e?: string;
|
|
34
|
-
/**
|
|
35
|
-
* The private key parameters.
|
|
36
|
-
*/
|
|
37
|
-
d?: string;
|
|
38
|
-
/**
|
|
39
|
-
* The private key parameters.
|
|
40
|
-
*/
|
|
41
|
-
p?: string;
|
|
42
|
-
/**
|
|
43
|
-
* The private key parameters.
|
|
44
|
-
*/
|
|
45
|
-
q?: string;
|
|
46
|
-
/**
|
|
47
|
-
* The private key parameters.
|
|
48
|
-
*/
|
|
49
|
-
dp?: string;
|
|
50
|
-
/**
|
|
51
|
-
* The private key parameters.
|
|
52
|
-
*/
|
|
53
|
-
dq?: string;
|
|
54
|
-
/**
|
|
55
|
-
* The private key parameters.
|
|
56
|
-
*/
|
|
57
|
-
qi?: string;
|
|
58
|
-
/**
|
|
59
|
-
* The key ID.
|
|
60
|
-
*/
|
|
61
|
-
kid?: string;
|
|
5
|
+
export interface IJwk extends JWK {
|
|
62
6
|
}
|