@twin.org/web 0.0.1-next.4 → 0.0.1-next.40

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.
@@ -1,5 +1,6 @@
1
- import { BaseError, StringHelper, Guards, Is, AsyncCache, ObjectHelper, Converter, GeneralError, ArrayHelper } from '@twin.org/core';
2
- import { HmacSha256, Ed25519 } from '@twin.org/crypto';
1
+ import { BaseError, StringHelper, Guards, Is, AsyncCache, ObjectHelper, GeneralError, Converter } from '@twin.org/core';
2
+ import { importJWK, SignJWT, jwtVerify } from 'jose';
3
+ import { Ed25519 } from '@twin.org/crypto';
3
4
 
4
5
  // Copyright 2024 IOTA Stiftung.
5
6
  // SPDX-License-Identifier: Apache-2.0.
@@ -37,35 +38,35 @@ const HeaderTypes = {
37
38
  /**
38
39
  * Content Type.
39
40
  */
40
- ContentType: "Content-Type",
41
+ ContentType: "content-type",
41
42
  /**
42
43
  * Content Length.
43
44
  */
44
- ContentLength: "Content-Length",
45
+ ContentLength: "content-length",
45
46
  /**
46
47
  * Content Disposition.
47
48
  */
48
- ContentDisposition: "Content-Disposition",
49
+ ContentDisposition: "content-disposition",
49
50
  /**
50
51
  * Accept.
51
52
  */
52
- Accept: "Accept",
53
+ Accept: "accept",
53
54
  /**
54
55
  * Authorization.
55
56
  */
56
- Authorization: "Authorization",
57
+ Authorization: "authorization",
57
58
  /**
58
59
  * Cookie.
59
60
  */
60
- Cookie: "Cookie",
61
+ Cookie: "cookie",
61
62
  /**
62
63
  * Set Cookie.
63
64
  */
64
- SetCookie: "Set-Cookie",
65
+ SetCookie: "set-cookie",
65
66
  /**
66
67
  * Location
67
68
  */
68
- Location: "Location"
69
+ Location: "location"
69
70
  };
70
71
 
71
72
  // Copyright 2024 IOTA Stiftung.
@@ -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
  */
@@ -708,7 +696,34 @@ class FetchHelper {
708
696
  // Copyright 2024 IOTA Stiftung.
709
697
  // SPDX-License-Identifier: Apache-2.0.
710
698
  /**
711
- * Class to encode and decode JSON Web Tokens.
699
+ * Class to handle JSON Web Keys.
700
+ */
701
+ class Jwk {
702
+ /**
703
+ * Runtime name for the class.
704
+ * @internal
705
+ */
706
+ static _CLASS_NAME = "Jwk";
707
+ /**
708
+ * Convert the JWK to a crypto key.
709
+ * @param jwk The JWK to convert.
710
+ * @returns The crypto key.
711
+ */
712
+ static async toCryptoKey(jwk) {
713
+ Guards.object(Jwk._CLASS_NAME, "jwk", jwk);
714
+ try {
715
+ return importJWK(jwk);
716
+ }
717
+ catch (err) {
718
+ throw new GeneralError(Jwk._CLASS_NAME, "jwkImportFailed", undefined, err);
719
+ }
720
+ }
721
+ }
722
+
723
+ // Copyright 2024 IOTA Stiftung.
724
+ // SPDX-License-Identifier: Apache-2.0.
725
+ /**
726
+ * Class to handle JSON Web Tokens.
712
727
  */
713
728
  class Jwt {
714
729
  /**
@@ -725,9 +740,9 @@ class Jwt {
725
740
  */
726
741
  static async encode(header, payload, key) {
727
742
  Guards.object(Jwt._CLASS_NAME, "header", header);
728
- Guards.arrayOneOf(Jwt._CLASS_NAME, "header.alg", header.alg, Object.values(JwtAlgorithms));
743
+ Guards.stringValue(Jwt._CLASS_NAME, "header.alg", header.alg);
729
744
  Guards.object(Jwt._CLASS_NAME, "payload", payload);
730
- Guards.uint8Array(Jwt._CLASS_NAME, "key", key);
745
+ Guards.defined(Jwt._CLASS_NAME, "key", key);
731
746
  return Jwt.internalEncode(header, payload, key);
732
747
  }
733
748
  /**
@@ -739,7 +754,7 @@ class Jwt {
739
754
  */
740
755
  static async encodeWithSigner(header, payload, signer) {
741
756
  Guards.object(Jwt._CLASS_NAME, "header", header);
742
- Guards.arrayOneOf(Jwt._CLASS_NAME, "header.alg", header.alg, Object.values(JwtAlgorithms));
757
+ Guards.stringValue(Jwt._CLASS_NAME, "header.alg", header.alg);
743
758
  Guards.object(Jwt._CLASS_NAME, "payload", payload);
744
759
  Guards.function(Jwt._CLASS_NAME, "signer", signer);
745
760
  return Jwt.internalEncode(header, payload, undefined, signer);
@@ -786,13 +801,8 @@ class Jwt {
786
801
  */
787
802
  static async verify(token, key) {
788
803
  Guards.stringValue(Jwt._CLASS_NAME, "token", token);
789
- Guards.uint8Array(Jwt._CLASS_NAME, "key", key);
790
- const decoded = await Jwt.decode(token);
791
- const verified = await Jwt.verifySignature(decoded.header, decoded.payload, decoded.signature, key);
792
- return {
793
- verified,
794
- ...decoded
795
- };
804
+ Guards.defined(Jwt._CLASS_NAME, "key", key);
805
+ return Jwt.verifySignature(token, key);
796
806
  }
797
807
  /**
798
808
  * Verify a token.
@@ -803,79 +813,131 @@ class Jwt {
803
813
  static async verifyWithVerifier(token, verifier) {
804
814
  Guards.stringValue(Jwt._CLASS_NAME, "token", token);
805
815
  Guards.function(Jwt._CLASS_NAME, "verifier", verifier);
806
- Guards.stringValue(Jwt._CLASS_NAME, "token", token);
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
- };
816
+ return Jwt.verifySignature(token, undefined, verifier);
813
817
  }
814
818
  /**
815
819
  * Verify a token by parts.
816
- * @param header The header to verify.
817
- * @param payload The payload to verify.
818
- * @param signature The signature to verify.
820
+ * @param token The token to verify.
819
821
  * @param key The key for verifying the token, if not provided no verification occurs.
820
822
  * @param verifier Custom verification method.
821
823
  * @returns True if the parts are verified.
822
824
  */
823
- static async verifySignature(header, payload, signature, key, verifier) {
825
+ static async verifySignature(token, key, verifier) {
826
+ Guards.stringValue(Jwt._CLASS_NAME, "token", token);
824
827
  const hasKey = Is.notEmpty(key);
825
828
  const hasVerifier = Is.notEmpty(verifier);
826
829
  if (!hasKey && !hasVerifier) {
827
830
  throw new GeneralError(Jwt._CLASS_NAME, "noKeyOrVerifier");
828
831
  }
829
- let verified = false;
830
- if (Is.object(header) &&
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;
832
+ verifier ??= async (t, k) => Jwt.defaultVerifier(t, k);
833
+ return verifier(token, key);
844
834
  }
845
835
  /**
846
836
  * The default signer for the JWT.
847
- * @param alg The algorithm to use.
848
- * @param key The key to sign with.
837
+ * @param header The header to sign.
849
838
  * @param payload The payload to sign.
839
+ * @param key The optional key to sign with.
850
840
  * @returns The signature.
851
841
  */
852
- static async defaultSigner(alg, key, payload) {
853
- Guards.uint8Array(Jwt._CLASS_NAME, "key", key);
854
- Guards.uint8Array(Jwt._CLASS_NAME, "payload", payload);
855
- if (alg === "HS256") {
856
- const algo = new HmacSha256(key);
857
- return algo.update(payload).digest();
842
+ static async defaultSigner(header, payload, key) {
843
+ Guards.object(Jwt._CLASS_NAME, "header", header);
844
+ Guards.object(Jwt._CLASS_NAME, "payload", payload);
845
+ Guards.defined(Jwt._CLASS_NAME, "key", key);
846
+ const signer = new SignJWT(payload);
847
+ signer.setProtectedHeader(header);
848
+ let finalKey = key;
849
+ if (header.alg === "EdDSA" && Is.uint8Array(key)) {
850
+ // Jose does not support Ed25519 keys in raw format, so we need to convert it to PKCS8.
851
+ finalKey = await Ed25519.privateKeyToPKCS8(key);
858
852
  }
859
- return Ed25519.sign(key, payload);
853
+ return signer.sign(finalKey);
860
854
  }
861
855
  /**
862
856
  * The default verifier for the JWT.
863
- * @param alg The algorithm to use.
857
+ * @param token The token to verify.
864
858
  * @param key The key to verify with.
865
- * @param payload The payload to verify.
866
- * @param signature The signature to verify.
867
- * @returns True if the signature was verified.
859
+ * @returns The header and payload if verification successful.
860
+ */
861
+ static async defaultVerifier(token, key) {
862
+ Guards.stringValue(Jwt._CLASS_NAME, "token", token);
863
+ Guards.defined(Jwt._CLASS_NAME, "key", key);
864
+ try {
865
+ const result = await jwtVerify(token, key);
866
+ return {
867
+ header: result.protectedHeader,
868
+ payload: result.payload
869
+ };
870
+ }
871
+ catch (err) {
872
+ throw new GeneralError(Jwt._CLASS_NAME, "verifyFailed", undefined, err);
873
+ }
874
+ }
875
+ /**
876
+ * Create bytes for signing from header and payload.
877
+ * @param header The header.
878
+ * @param payload The payload.
879
+ * @returns The bytes to sign.
868
880
  */
869
- static async defaultVerifier(alg, key, payload, signature) {
870
- Guards.uint8Array(Jwt._CLASS_NAME, "key", key);
871
- Guards.uint8Array(Jwt._CLASS_NAME, "payload", payload);
881
+ static toSigningBytes(header, payload) {
882
+ Guards.object(Jwt._CLASS_NAME, "header", header);
883
+ Guards.object(Jwt._CLASS_NAME, "payload", payload);
884
+ const segments = [];
885
+ const headerBytes = Converter.utf8ToBytes(JSON.stringify(header));
886
+ segments.push(Converter.bytesToBase64Url(headerBytes));
887
+ const payloadBytes = Converter.utf8ToBytes(JSON.stringify(payload));
888
+ segments.push(Converter.bytesToBase64Url(payloadBytes));
889
+ return Converter.utf8ToBytes(segments.join("."));
890
+ }
891
+ /**
892
+ * Create header and payload from signing bytes.
893
+ * @param signingBytes The signing bytes from a token.
894
+ * @returns The header and payload.
895
+ * @throws If the signing bytes are invalid
896
+ */
897
+ static fromSigningBytes(signingBytes) {
898
+ Guards.uint8Array(Jwt._CLASS_NAME, "signingBytes", signingBytes);
899
+ const segments = Converter.bytesToUtf8(signingBytes).split(".");
900
+ if (segments.length !== 2) {
901
+ throw new GeneralError(Jwt._CLASS_NAME, "invalidSigningBytes");
902
+ }
903
+ const headerBytes = Converter.base64UrlToBytes(segments[0]);
904
+ const payloadBytes = Converter.base64UrlToBytes(segments[1]);
905
+ return {
906
+ header: ObjectHelper.fromBytes(headerBytes),
907
+ payload: ObjectHelper.fromBytes(payloadBytes)
908
+ };
909
+ }
910
+ /**
911
+ * Convert signed bytes and signature bytes to token.
912
+ * @param signingBytes The signed bytes.
913
+ * @param signature The signature.
914
+ * @returns The token.
915
+ */
916
+ static tokenFromBytes(signingBytes, signature) {
917
+ Guards.uint8Array(Jwt._CLASS_NAME, "signingBytes", signingBytes);
872
918
  Guards.uint8Array(Jwt._CLASS_NAME, "signature", signature);
873
- if (alg === "HS256") {
874
- const algo = new HmacSha256(key);
875
- const sigBytes = algo.update(payload).digest();
876
- return ArrayHelper.matches(sigBytes, signature);
919
+ const signedBytesUtf8 = Converter.bytesToUtf8(signingBytes);
920
+ const signatureBase64 = Converter.bytesToBase64Url(signature);
921
+ return `${signedBytesUtf8}.${signatureBase64}`;
922
+ }
923
+ /**
924
+ * Convert the token to signing bytes and signature bytes.
925
+ * @param token The token to convert to bytes.
926
+ * @returns The decoded bytes.
927
+ * @throws If the token is invalid.
928
+ */
929
+ static tokenToBytes(token) {
930
+ Guards.stringValue(Jwt._CLASS_NAME, "token", token);
931
+ const segments = token.split(".");
932
+ if (segments.length !== 3) {
933
+ throw new GeneralError(Jwt._CLASS_NAME, "invalidTokenParts");
877
934
  }
878
- return Ed25519.verify(key, payload, signature);
935
+ const signingBytes = Converter.utf8ToBytes(`${segments[0]}.${segments[1]}`);
936
+ const signature = Converter.base64UrlToBytes(segments[2]);
937
+ return {
938
+ signingBytes,
939
+ signature
940
+ };
879
941
  }
880
942
  /**
881
943
  * Encode a token.
@@ -892,19 +954,11 @@ class Jwt {
892
954
  if (!hasKey && !hasSigner) {
893
955
  throw new GeneralError(Jwt._CLASS_NAME, "noKeyOrSigner");
894
956
  }
895
- signer ??= async (alg, k, p) => Jwt.defaultSigner(alg, k, p);
957
+ signer ??= async (h, p, k) => Jwt.defaultSigner(h, p, k);
896
958
  if (Is.undefined(header.typ)) {
897
959
  header.typ = "JWT";
898
960
  }
899
- const segments = [];
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(".");
961
+ return signer(header, payload, key);
908
962
  }
909
963
  }
910
964
 
@@ -920,7 +974,7 @@ class MimeTypeHelper {
920
974
  * @returns The mime type if detected.
921
975
  */
922
976
  static async detect(data) {
923
- if (!Is.uint8Array(data)) {
977
+ if (!Is.uint8Array(data) || data.length === 0) {
924
978
  return undefined;
925
979
  }
926
980
  // Image
@@ -992,12 +1046,13 @@ class MimeTypeHelper {
992
1046
  [MimeTypes.Javascript]: "js",
993
1047
  [MimeTypes.Json]: "json",
994
1048
  [MimeTypes.JsonLd]: "jsonld",
1049
+ [MimeTypes.Jwt]: "jwt",
995
1050
  [MimeTypes.Xml]: "xml",
996
1051
  [MimeTypes.OctetStream]: "bin",
997
1052
  [MimeTypes.Gzip]: "gzip",
998
1053
  [MimeTypes.Bzip2]: "bz2",
999
1054
  [MimeTypes.Zip]: "zip",
1000
- [MimeTypes.Pdf]: "pfd",
1055
+ [MimeTypes.Pdf]: "pdf",
1001
1056
  [MimeTypes.Gif]: "gif",
1002
1057
  [MimeTypes.Bmp]: "bmp",
1003
1058
  [MimeTypes.Jpeg]: "jpeg",
@@ -1043,4 +1098,4 @@ class MimeTypeHelper {
1043
1098
  }
1044
1099
  }
1045
1100
 
1046
- export { FetchError, FetchHelper, HeaderTypes, HttpMethod, HttpStatusCode, Jwt, JwtAlgorithms, MimeTypeHelper, MimeTypes };
1101
+ export { FetchError, FetchHelper, HeaderTypes, HttpMethod, HttpStatusCode, Jwk, Jwt, MimeTypeHelper, MimeTypes };
@@ -7,8 +7,9 @@ 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/jwtAlgorithms";
10
+ export * from "./models/jwkCryptoKey";
11
11
  export * from "./models/mimeTypes";
12
12
  export * from "./utils/fetchHelper";
13
+ export * from "./utils/jwk";
13
14
  export * from "./utils/jwt";
14
15
  export * from "./utils/mimeTypeHelper";
@@ -1,62 +1,6 @@
1
- import type { JwtAlgorithms } from "./jwtAlgorithms";
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
  }
@@ -1,22 +1,6 @@
1
- import type { JwtAlgorithms } from "./jwtAlgorithms";
1
+ import type { JWTHeaderParameters } from "jose";
2
2
  /**
3
3
  * The fields in a JSON Web Token header.
4
4
  */
5
- export interface IJwtHeader {
6
- /**
7
- * Additional fields in the header.
8
- */
9
- [key: string]: unknown;
10
- /**
11
- * The type of the token.
12
- */
13
- typ?: string;
14
- /**
15
- * The algorithm used to sign the token.
16
- */
17
- alg: JwtAlgorithms;
18
- /**
19
- * The key ID.
20
- */
21
- kid?: string;
5
+ export interface IJwtHeader extends JWTHeaderParameters {
22
6
  }
@@ -1,37 +1,6 @@
1
+ import type { JWTPayload } from "jose";
1
2
  /**
2
3
  * The fields in a JSON Web Token payload.
3
4
  */
4
- export interface IJwtPayload {
5
- /**
6
- * Additional fields in the payload.
7
- */
8
- [key: string]: unknown;
9
- /**
10
- * The issuer of the token.
11
- */
12
- iss?: string;
13
- /**
14
- * The subject of the token.
15
- */
16
- sub?: string;
17
- /**
18
- * The audience of the token.
19
- */
20
- aud?: string;
21
- /**
22
- * The expiration time of the token.
23
- */
24
- exp?: number;
25
- /**
26
- * The not before time of the token.
27
- */
28
- nbf?: number;
29
- /**
30
- * The issued at time of the token.
31
- */
32
- iat?: number;
33
- /**
34
- * The JWT ID.
35
- */
36
- jti?: string;
5
+ export interface IJwtPayload extends JWTPayload {
37
6
  }
@@ -5,35 +5,35 @@ export declare const HeaderTypes: {
5
5
  /**
6
6
  * Content Type.
7
7
  */
8
- readonly ContentType: "Content-Type";
8
+ readonly ContentType: "content-type";
9
9
  /**
10
10
  * Content Length.
11
11
  */
12
- readonly ContentLength: "Content-Length";
12
+ readonly ContentLength: "content-length";
13
13
  /**
14
14
  * Content Disposition.
15
15
  */
16
- readonly ContentDisposition: "Content-Disposition";
16
+ readonly ContentDisposition: "content-disposition";
17
17
  /**
18
18
  * Accept.
19
19
  */
20
- readonly Accept: "Accept";
20
+ readonly Accept: "accept";
21
21
  /**
22
22
  * Authorization.
23
23
  */
24
- readonly Authorization: "Authorization";
24
+ readonly Authorization: "authorization";
25
25
  /**
26
26
  * Cookie.
27
27
  */
28
- readonly Cookie: "Cookie";
28
+ readonly Cookie: "cookie";
29
29
  /**
30
30
  * Set Cookie.
31
31
  */
32
- readonly SetCookie: "Set-Cookie";
32
+ readonly SetCookie: "set-cookie";
33
33
  /**
34
34
  * Location
35
35
  */
36
- readonly Location: "Location";
36
+ readonly Location: "location";
37
37
  };
38
38
  /**
39
39
  * Common http header types.
@@ -0,0 +1,4 @@
1
+ /**
2
+ * The crypto key for a JWK.
3
+ */
4
+ export type JwkCryptoKey = CryptoKey | Uint8Array;
@@ -22,6 +22,10 @@ export declare const MimeTypes: {
22
22
  * JSON-LD - application/ld+json
23
23
  */
24
24
  readonly JsonLd: "application/ld+json";
25
+ /**
26
+ * JWT - application/jwt
27
+ */
28
+ readonly Jwt: "application/jwt";
25
29
  /**
26
30
  * XML - application/xml
27
31
  */
@@ -0,0 +1,13 @@
1
+ import type { IJwk } from "../models/IJwk";
2
+ import type { JwkCryptoKey } from "../models/jwkCryptoKey";
3
+ /**
4
+ * Class to handle JSON Web Keys.
5
+ */
6
+ export declare class Jwk {
7
+ /**
8
+ * Convert the JWK to a crypto key.
9
+ * @param jwk The JWK to convert.
10
+ * @returns The crypto key.
11
+ */
12
+ static toCryptoKey(jwk: IJwk): Promise<JwkCryptoKey>;
13
+ }