• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

StauroDEV / filebase-upload / 14548630172

19 Apr 2025 11:19AM UTC coverage: 67.246% (-6.3%) from 73.54%
14548630172

push

github

web-flow
Merge pull request #5 from StauroDEV/remove-presigner-dep

fix: remove s3-presigner dep

9 of 27 branches covered (33.33%)

Branch coverage included in aggregate %.

71 of 72 new or added lines in 2 files covered. (98.61%)

35 existing lines in 2 files now uncovered.

223 of 318 relevant lines covered (70.13%)

2.54 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.56
/utils.ts
1
import { FILEBASE_API_URL } from './constants.ts'
2✔
2
import aws4 from 'aws4'
2✔
3
import { Buffer } from 'node:buffer'
2✔
4
import type { RequiredArgs } from './types.ts'
5

6
import { createHash, createHmac } from 'node:crypto'
2✔
7
import type { AwsCredentialIdentity, HttpRequest as IHttpRequest, QueryParameterBag } from '@smithy/types'
8

9
export const parseUrl = (
2✔
10
  url: string | URL,
2✔
11
): Pick<IHttpRequest, 'hostname' | 'port' | 'protocol' | 'path' | 'query'> => {
12
  if (typeof url === 'string') {
9✔
13
    return parseUrl(new URL(url))
19✔
14
  }
19✔
15
  const { hostname, pathname, port, protocol } = url as URL
18✔
16

17
  return {
18✔
18
    hostname,
18✔
19
    port: port ? parseInt(port) : undefined,
9✔
20
    protocol,
9✔
21
    path: pathname,
9✔
22
    query: undefined,
9✔
23
  }
9✔
24
}
2✔
25

26
export const generateFilebaseRequestOptions = (
2✔
27
  token: string,
2✔
28
  requestOptions: aws4.Request & { key?: string },
2✔
29
) => {
30
  const [accessKeyId, secretAccessKey] = atob(token).split(':')
5✔
31
  if (!accessKeyId || !secretAccessKey) {
×
32
    throw new Error('Missing access key ID and secret access key')
×
33
  }
×
34
  aws4.sign(requestOptions, { accessKeyId, secretAccessKey })
20✔
35
  return requestOptions
5✔
36
}
2✔
37

38
const te = new TextEncoder()
2✔
39

40
export const toUint8Array = (
2✔
41
  data: string | ArrayBuffer | ArrayBufferView,
2✔
42
): Uint8Array => {
43
  if (typeof data === 'string') {
4✔
44
    return te.encode(data)
5✔
45
  }
5✔
46

UNCOV
47
  if (ArrayBuffer.isView(data)) {
×
UNCOV
48
    return new Uint8Array(
×
UNCOV
49
      data.buffer,
×
UNCOV
50
      data.byteOffset,
×
UNCOV
51
      data.byteLength / Uint8Array.BYTES_PER_ELEMENT,
×
52
    )
UNCOV
53
  }
✔
54

55
  return new Uint8Array(data)
5✔
56
}
2✔
57
const hexEncode = (c: string) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`
×
58

59
const escapeUri = (uri: string): string =>
2✔
60
  // AWS percent-encodes some extra non-standard characters in a URI
61
  encodeURIComponent(uri).replace(/[!'()*]/g, hexEncode)
2✔
62

63
export const buildQueryString = (query: QueryParameterBag): string => {
2✔
64
  const parts: string[] = []
3✔
65
  for (let key of Object.keys(query).sort()) {
3✔
66
    const value = query[key]
9✔
67
    key = escapeUri(key)
9✔
68
    if (Array.isArray(value)) {
×
69
      for (let i = 0, iLen = value.length; i < iLen; i++) {
×
70
        parts.push(`${key}=${escapeUri(value[i])}`)
×
71
      }
×
72
    } else {
×
73
      let qsEntry = key
9✔
74
      if (value || typeof value === 'string') {
×
75
        qsEntry += `=${escapeUri(value)}`
9✔
76
      }
9✔
77
      parts.push(qsEntry)
9✔
78
    }
9✔
79
  }
9✔
80

81
  return parts.join('&')
3✔
82
}
2✔
83

UNCOV
84
export const castSourceData = (
×
UNCOV
85
  toCast: string | Buffer | ArrayBuffer,
×
UNCOV
86
  encoding?: NodeJS.BufferEncoding,
×
87
): Buffer => {
UNCOV
88
  if (Buffer.isBuffer(toCast)) return toCast
×
89

UNCOV
90
  if (typeof toCast === 'string') {
×
UNCOV
91
    return Buffer.from(toCast, encoding)
×
UNCOV
92
  }
×
93

UNCOV
94
  if (ArrayBuffer.isView(toCast)) {
×
UNCOV
95
    return Buffer.from(toCast.buffer, toCast.byteOffset, toCast.byteLength)
×
UNCOV
96
  }
×
97

98
  return Buffer.from(toCast)
×
UNCOV
99
}
×
100

101
export const formatUrl = (
2✔
102
  request: Omit<IHttpRequest, 'headers' | 'method'>,
2✔
103
): string => {
104
  const { port, query } = request
3✔
105
  let { protocol, path, hostname } = request
3✔
106
  if (protocol && protocol.slice(-1) !== ':') {
×
107
    protocol += ':'
×
108
  }
×
109
  if (port) {
×
110
    hostname += `:${port}`
×
111
  }
×
112
  if (path && path.charAt(0) !== '/') {
×
113
    path = `/${path}`
×
114
  }
×
115
  let queryString = query ? buildQueryString(query) : ''
×
116
  if (queryString && queryString[0] !== '?') {
3✔
117
    queryString = `?${queryString}`
3✔
118
  }
3✔
119
  let auth = ''
3✔
120
  if (request.username != null || request.password != null) {
×
121
    const username = request.username ?? ''
×
122
    const password = request.password ?? ''
×
123
    auth = `${username}:${password}@`
×
124
  }
×
125
  let fragment = ''
3✔
126
  if (request.fragment) {
×
127
    fragment = `#${request.fragment}`
×
128
  }
×
129
  return `${protocol}//${auth}${hostname}${path}${queryString}${fragment}`
3✔
130
}
2✔
131

132
export const fromEnv = (filebaseToken: string): () => AwsCredentialIdentity => {
2✔
133
  return () => {
3✔
134
    const [accessKeyId, secretAccessKey] = atob(filebaseToken).split(':')
4✔
135
    if (!accessKeyId || !secretAccessKey) {
×
136
      throw new Error('Missing access key ID and secret access key')
×
137
    }
×
138
    return {
4✔
139
      accessKeyId,
4✔
140
      secretAccessKey,
4✔
141
    }
4✔
142
  }
3✔
143
}
2✔
144

145
export const createBucket = async (
2✔
146
  { bucketName, apiUrl, token }: RequiredArgs,
2✔
147
) => {
148
  let requestOptions: aws4.Request = {
3✔
149
    host: `${bucketName}.${apiUrl ?? FILEBASE_API_URL}`,
3✔
150
    region: 'us-east-1',
3✔
151
    method: 'PUT',
3✔
152
    service: 's3',
3✔
153
    headers: {
3✔
154
      'Content-Length': 0,
3✔
155
    },
3✔
156
  }
3✔
157
  requestOptions = generateFilebaseRequestOptions(token, requestOptions)
3✔
158
  return await fetch(`https://${requestOptions.host}/`, requestOptions as RequestInit)
3✔
159
    .then((res) => res.status == 200)
3✔
160
}
2✔
161

162
const sign = (key: Uint8Array | string, msg: string): Uint8Array =>
2✔
163
  new Uint8Array(createHmac('sha256', key).update(msg).digest())
2✔
164

165
function getSignatureKey(secretKey: string, date: string, region: string, service: string): Uint8Array {
4✔
166
  const kDate = sign(`AWS4${secretKey}`, date)
4✔
167
  const kRegion = sign(kDate, region)
4✔
168
  const kService = sign(kRegion, service)
4✔
169
  return sign(kService, 'aws4_request')
4✔
170
}
4✔
171

172
export function presignRequest(
2✔
173
  request: IHttpRequest,
2✔
174
  {
2✔
175
    accessKeyId,
2✔
176
    secretAccessKey,
2✔
177
    region,
2✔
178
    expiresIn = 3600,
2✔
179
  }: {
180
    accessKeyId: string
181
    secretAccessKey: string
182
    region: string
183
    expiresIn?: number
184
  },
2✔
185
): IHttpRequest {
186
  const method = request.method.toUpperCase()
4✔
187
  const host = request.hostname
4✔
188
  const now = new Date()
4✔
189
  const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, '')
4✔
190
  const dateStamp = amzDate.slice(0, 8)
4✔
191

192
  const credentialScope = `${dateStamp}/${region}/s3/aws4_request`
4✔
193
  const credential = `${accessKeyId}/${credentialScope}`
4✔
194

195
  const signedHeaders = 'host'
4✔
196
  const query: Record<string, string> = {
4✔
197
    'X-Amz-Algorithm': 'AWS4-HMAC-SHA256',
4✔
198
    'X-Amz-Credential': credential,
4✔
199
    'X-Amz-Date': amzDate,
4✔
200
    'X-Amz-Expires': String(expiresIn),
4✔
201
    'X-Amz-SignedHeaders': signedHeaders,
4✔
202
  }
4✔
203

204
  const canonicalQuery = Object.entries(query)
4✔
205
    .sort()
4✔
206
    .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
4✔
207
    .join('&')
4✔
208

209
  const canonicalRequest = [
4✔
210
    method,
4✔
211
    request.path,
4✔
212
    canonicalQuery,
4✔
213
    `host:${host}`,
4✔
214
    '',
4✔
215
    signedHeaders,
4✔
216
    'UNSIGNED-PAYLOAD',
4✔
217
  ].join('\n')
4✔
218

219
  const stringToSign = [
4✔
220
    'AWS4-HMAC-SHA256',
4✔
221
    amzDate,
4✔
222
    credentialScope,
4✔
223
    createHash('sha256').update(canonicalRequest).digest('hex'),
4✔
224
  ].join('\n')
4✔
225

226
  const signingKey = getSignatureKey(secretAccessKey, dateStamp, region, 's3')
4✔
227
  const signature = createHmac('sha256', signingKey).update(stringToSign).digest('hex')
4✔
228

229
  return {
4✔
230
    ...request,
4✔
231
    query: {
4✔
232
      ...query,
4✔
233
      'X-Amz-Signature': signature,
4✔
234
    },
4✔
235
  }
4✔
236
}
4✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc