11import * as crypto from 'node:crypto' ;
2- import * as queryString from 'node:querystring' ;
32
43export interface AWS4 {
54 /**
@@ -42,6 +41,29 @@ export interface AWS4 {
4241 } ;
4342}
4443
44+ const getHash = ( str : string ) : string => {
45+ return crypto . createHash ( 'sha256' ) . update ( str , 'utf8' ) . digest ( 'hex' ) ;
46+ } ;
47+ const getHmacArray = ( key : string | Uint8Array , str : string ) : Uint8Array => {
48+ return crypto . createHmac ( 'sha256' , key ) . update ( str , 'utf8' ) . digest ( ) ;
49+ } ;
50+ const getHmacString = ( key : Uint8Array , str : string ) : string => {
51+ return crypto . createHmac ( 'sha256' , key ) . update ( str , 'utf8' ) . digest ( 'hex' ) ;
52+ } ;
53+
54+ const getEnvCredentials = ( ) => {
55+ const env = process . env ;
56+ return {
57+ accessKeyId : env . AWS_ACCESS_KEY_ID || env . AWS_ACCESS_KEY ,
58+ secretAccessKey : env . AWS_SECRET_ACCESS_KEY || env . AWS_SECRET_KEY ,
59+ sessionToken : env . AWS_SESSION_TOKEN
60+ } ;
61+ } ;
62+
63+ const convertHeaderValue = ( value : string | number ) => {
64+ return value . toString ( ) . trim ( ) . replace ( / \s + / g, ' ' ) ;
65+ } ;
66+
4567export function aws4Sign (
4668 this : void ,
4769 options : {
@@ -75,118 +97,56 @@ export function aws4Sign(
7597 'X-Amz-Date' : string ;
7698 } ;
7799} {
78- let path : string ;
79- let query : queryString . ParsedUrlQuery | undefined ;
80-
81- const encode = ( str : string ) => {
82- const encoded = encodeURIComponent ( str ) ;
83- const replaced = encoded . replace ( / [ ! ' ( ) * ] / g, function ( c ) {
84- return '%' + c . charCodeAt ( 0 ) . toString ( 16 ) . toUpperCase ( ) ;
85- } ) ;
86- return replaced ;
87- } ;
88-
89- const queryIndex = options . path . indexOf ( '?' ) ;
90- if ( queryIndex < 0 ) {
91- path = options . path ;
92- query = undefined ;
93- } else {
94- path = options . path . slice ( 0 , queryIndex ) ;
95- query = queryString . parse ( options . path . slice ( queryIndex + 1 ) ) ;
96- }
97-
98- let canonicalQuerystring = '' ;
99- if ( query ) {
100- const isS3 = options . service === 's3' ;
101- const useFirstArrayValue = isS3 ;
102- // const decodeSlashesInPath = isS3;
103- // const decodePath = isS3;
104- // const normalizePath = !isS3;
105- const queryStrings : string [ ] = [ ] ;
106- const sortedQueryKeys = Object . keys ( query ) . sort ( ) ;
107- for ( const key of sortedQueryKeys ) {
108- if ( ! key ) {
109- continue ;
110- }
111-
112- const encodedKey = encode ( key ) ;
113- let value : string | string [ ] | undefined = query [ key ] ;
114- if ( Array . isArray ( value ) ) {
115- let values : string [ ] = value ;
116- if ( useFirstArrayValue ) {
117- values = [ value [ 0 ] ] ;
118- }
100+ const method = options . method ;
101+ const canonicalUri = options . path ;
102+ const canonicalQuerystring = '' ;
103+ const creds = credentials || getEnvCredentials ( ) ;
119104
120- for ( const item of values ) {
121- const encodedValue = encode ( item ) ;
122- queryStrings . push ( `${ encodedKey } =${ encodedValue } ` ) ;
123- }
124- } else {
125- value = value ?? '' ;
126- const encodedValue = encode ( value ) ;
127- queryStrings . push ( `${ encodedKey } =${ encodedValue } ` ) ;
128- }
129- }
130- canonicalQuerystring = queryStrings . join ( '&' ) ;
131- }
105+ const date = new Date ( ) ;
106+ const requestDateTime = date . toISOString ( ) . replace ( / [: -] | \. \d { 3 } / g, '' ) ;
107+ const requestDate = requestDateTime . substring ( 0 , 8 ) ;
132108
133- const convertHeaderValue = ( value : string | number ) => {
134- return value . toString ( ) . trim ( ) . replace ( / \s + / g, ' ' ) ;
135- } ;
136109 const headers : string [ ] = [
137- `content-length:${ convertHeaderValue ( options . headers [ 'Content-Length' ] ) } \n` ,
138- `content-type:${ convertHeaderValue ( options . headers [ 'Content-Type' ] ) } \n` ,
139- `x-mongodb-gs2-cb-flag:${ convertHeaderValue ( options . headers [ 'X-MongoDB-GS2-CB-Flag' ] ) } \n` ,
140- `x-mongodb-server-nonce:${ convertHeaderValue ( options . headers [ 'X-MongoDB-Server-Nonce' ] ) } \n`
110+ `content-length:${ convertHeaderValue ( options . headers [ 'Content-Length' ] ) } ` ,
111+ `content-type:${ convertHeaderValue ( options . headers [ 'Content-Type' ] ) } ` ,
112+ `host:${ convertHeaderValue ( options . host ) } ` ,
113+ `x-amz-date:${ convertHeaderValue ( requestDateTime ) } ` ,
114+ `x-mongodb-gs2-cb-flag:${ convertHeaderValue ( options . headers [ 'X-MongoDB-GS2-CB-Flag' ] ) } ` ,
115+ `x-mongodb-server-nonce:${ convertHeaderValue ( options . headers [ 'X-MongoDB-Server-Nonce' ] ) } `
141116 ] ;
117+ if ( 'sessionToken' in creds && creds . sessionToken ) {
118+ headers . push ( `x-amz-security-token:${ convertHeaderValue ( creds . sessionToken ) } ` ) ;
119+ }
142120 const canonicalHeaders = headers . sort ( ) . join ( '\n' ) ;
121+ const canonicalHeaderNames = headers . map ( header => header . split ( ':' , 2 ) [ 0 ] . toLowerCase ( ) ) ;
122+ const signedHeaders = canonicalHeaderNames . sort ( ) . join ( ';' ) ;
143123
144- const signedHeaders = 'content-length;content-type;x-mongodb-gs2-cb-flag;x-mongodb-server-nonce' ;
145-
146- const getHash = ( str : string ) : string => {
147- return crypto . createHash ( 'sha256' ) . update ( str , 'utf8' ) . digest ( 'hex' ) ;
148- } ;
149- const getHmac = ( key : string , str : string ) : string => {
150- return crypto . createHmac ( 'sha256' , key ) . update ( str , 'utf8' ) . digest ( 'hex' ) ;
151- } ;
152124 const hashedPayload = getHash ( options . body || '' ) ;
153125
154- const canonicalUri = path ;
155126 const canonicalRequest = [
156- options . method ,
127+ method ,
157128 canonicalUri ,
158129 canonicalQuerystring ,
159- canonicalHeaders ,
130+ canonicalHeaders + '\n' ,
160131 signedHeaders ,
161132 hashedPayload
162133 ] . join ( '\n' ) ;
163134
164- const canonicRequestHash = getHash ( canonicalRequest ) ;
165- const requestDateTime = new Date ( ) . toISOString ( ) . replace ( / [: -] | \. \d { 3 } / g, '' ) ;
166- const requestDate = requestDateTime . substring ( 0 , 8 ) ;
135+ const canonicalRequestHash = getHash ( canonicalRequest ) ;
167136 const credentialScope = `${ requestDate } /${ options . region } /${ options . service } /aws4_request` ;
168137
169138 const stringToSign = [
170139 'AWS4-HMAC-SHA256' ,
171140 requestDateTime ,
172141 credentialScope ,
173- canonicRequestHash
142+ canonicalRequestHash
174143 ] . join ( '\n' ) ;
175144
176- const getEnvCredentials = ( ) => {
177- const env = process . env ;
178- return {
179- accessKeyId : env . AWS_ACCESS_KEY_ID || env . AWS_ACCESS_KEY ,
180- secretAccessKey : env . AWS_SECRET_ACCESS_KEY || env . AWS_SECRET_KEY ,
181- sessionToken : env . AWS_SESSION_TOKEN
182- } ;
183- } ;
184- const creds = credentials || getEnvCredentials ( ) ;
185- const dateKey = getHmac ( 'AWS4' + creds . secretAccessKey , requestDate ) ;
186- const dateRegionKey = getHmac ( dateKey , options . region ) ;
187- const dateRegionServiceKey = getHmac ( dateRegionKey , options . service ) ;
188- const signingKey = getHmac ( dateRegionServiceKey , 'aws4_request' ) ;
189- const signature = getHmac ( signingKey , stringToSign ) ;
145+ const dateKey = getHmacArray ( 'AWS4' + creds . secretAccessKey , requestDate ) ;
146+ const dateRegionKey = getHmacArray ( dateKey , options . region ) ;
147+ const dateRegionServiceKey = getHmacArray ( dateRegionKey , options . service ) ;
148+ const signingKey = getHmacArray ( dateRegionServiceKey , 'aws4_request' ) ;
149+ const signature = getHmacString ( signingKey , stringToSign ) ;
190150
191151 const authorizationHeader = [
192152 'AWS4-HMAC-SHA256 Credential=' + creds . accessKeyId + '/' + credentialScope ,
@@ -201,7 +161,3 @@ export function aws4Sign(
201161 }
202162 } ;
203163}
204-
205- // export const aws4: AWS4 = {
206- // sign: aws4Sign
207- // };
0 commit comments