1- import { TokenSearchRules , TokenOptions } from "./types" ;
2- import { MeiliSearchError } from "./errors" ;
3- import { validateUuid4 } from "./utils" ;
1+ import type { TokenSearchRules , TokenOptions } from "./types" ;
42
5- function encode64 ( data : unknown ) : string {
6- return btoa ( JSON . stringify ( data ) ) ;
3+ const UUID_V4_REGEXP = / ^ [ 0 - 9 a - f ] { 8 } \b (?: - [ 0 - 9 a - f ] { 4 } \b ) { 3 } - [ 0 - 9 a - f ] { 12 } $ / i;
4+ function isValidUUIDv4 ( uuid : string ) : boolean {
5+ return UUID_V4_REGEXP . test ( uuid ) ;
76}
87
8+ function encodeToBase64 ( data : unknown ) : string {
9+ // TODO: instead of btoa use Uint8Array.prototype.toBase64() when it becomes available in supported runtime versions
10+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/toBase64
11+ return btoa ( typeof data === "string" ? data : JSON . stringify ( data ) ) ;
12+ }
13+
14+ // missing crypto global for Node.js 18 https://nodejs.org/api/globals.html#crypto_1
15+ // TODO: Improve error handling?
16+ const compatCrypto =
17+ typeof crypto === "undefined"
18+ ? ( await import ( "node:crypto" ) ) . webcrypto
19+ : crypto ;
20+
21+ const textEncoder = new TextEncoder ( ) ;
22+
923/**
1024 * Create the header of the token.
1125 *
@@ -19,29 +33,21 @@ async function sign(
1933 encodedHeader : string ,
2034 encodedPayload : string ,
2135) : Promise < string > {
22- // missing crypto global for Node.js 18
23- const localCrypto =
24- typeof crypto === "undefined"
25- ? // @ts -expect-error: Need to add @types/node for this and remove dom lib
26- ( await import ( "node:crypto" ) ) . webcrypto
27- : crypto ;
28-
29- const textEncoder = new TextEncoder ( ) ;
30-
31- const cryptoKey = await localCrypto . subtle . importKey (
36+ const cryptoKey = await compatCrypto . subtle . importKey (
3237 "raw" ,
3338 textEncoder . encode ( apiKey ) ,
3439 { name : "HMAC" , hash : "SHA-256" } ,
3540 false ,
3641 [ "sign" ] ,
3742 ) ;
3843
39- const signature = await localCrypto . subtle . sign (
44+ const signature = await compatCrypto . subtle . sign (
4045 "HMAC" ,
4146 cryptoKey ,
4247 textEncoder . encode ( `${ encodedHeader } .${ encodedPayload } ` ) ,
4348 ) ;
4449
50+ // TODO: Same problem as in `encodeToBase64` above
4551 const digest = btoa ( String . fromCharCode ( ...new Uint8Array ( signature ) ) )
4652 . replace ( / \+ / g, "-" )
4753 . replace ( / \/ / g, "_" )
@@ -55,63 +61,13 @@ async function sign(
5561 *
5662 * @returns The header encoded in base64.
5763 */
58- function createHeader ( ) {
64+ function createHeader ( ) : string {
5965 const header = {
6066 alg : "HS256" ,
6167 typ : "JWT" ,
6268 } ;
6369
64- return encode64 ( header ) . replace ( / = / g, "" ) ;
65- }
66-
67- /**
68- * Validate the parameter used for the payload of the token.
69- *
70- * @param searchRules - Search rules that are applied to every search.
71- * @param apiKey - Api key used as issuer of the token.
72- * @param uid - The uid of the api key used as issuer of the token.
73- * @param expiresAt - Date at which the token expires.
74- */
75- function validateTokenParameters ( {
76- searchRules,
77- apiKeyUid,
78- expiresAt,
79- } : {
80- searchRules : TokenSearchRules ;
81- apiKeyUid : string ;
82- expiresAt ?: Date ;
83- } ) {
84- if ( expiresAt ) {
85- if ( ! ( expiresAt instanceof Date ) ) {
86- throw new MeiliSearchError (
87- `Meilisearch: The expiredAt field must be an instance of Date.` ,
88- ) ;
89- } else if ( expiresAt . getTime ( ) < Date . now ( ) ) {
90- throw new MeiliSearchError (
91- `Meilisearch: The expiresAt field must be a date in the future.` ,
92- ) ;
93- }
94- }
95-
96- if ( searchRules ) {
97- if ( ! ( typeof searchRules === "object" || Array . isArray ( searchRules ) ) ) {
98- throw new MeiliSearchError (
99- `Meilisearch: The search rules added in the token generation must be of type array or object.` ,
100- ) ;
101- }
102- }
103-
104- if ( ! apiKeyUid || typeof apiKeyUid !== "string" ) {
105- throw new MeiliSearchError (
106- `Meilisearch: The uid of the api key used for the token generation must exist, be of type string and comply to the uuid4 format.` ,
107- ) ;
108- }
109-
110- if ( ! validateUuid4 ( apiKeyUid ) ) {
111- throw new MeiliSearchError (
112- `Meilisearch: The uid of your key is not a valid uuid4. To find out the uid of your key use getKey().` ,
113- ) ;
114- }
70+ return encodeToBase64 ( header ) . replace ( / = / g, "" ) ;
11571}
11672
11773/**
@@ -137,7 +93,7 @@ function createPayload({
13793 exp : expiresAt ? Math . floor ( expiresAt . getTime ( ) / 1000 ) : undefined ,
13894 } ;
13995
140- return encode64 ( payload ) . replace ( / = / g, "" ) ;
96+ return encodeToBase64 ( payload ) . replace ( / = / g, "" ) ;
14197}
14298
14399/**
@@ -153,7 +109,15 @@ export async function generateTenantToken(
153109 searchRules : TokenSearchRules ,
154110 { apiKey, expiresAt } : TokenOptions ,
155111) : Promise < string > {
156- validateTokenParameters ( { apiKeyUid, expiresAt, searchRules } ) ;
112+ if ( expiresAt !== undefined && expiresAt . getTime ( ) < Date . now ( ) ) {
113+ throw new Error ( "the `expiresAt` field must be a date in the future" ) ;
114+ }
115+
116+ if ( ! isValidUUIDv4 ( apiKeyUid ) ) {
117+ throw new Error (
118+ "the uid of your key is not a valid UUIDv4; to find out the uid of your key use `getKey()`" ,
119+ ) ;
120+ }
157121
158122 const encodedHeader = createHeader ( ) ;
159123 const encodedPayload = createPayload ( {
0 commit comments