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

StauroDEV / filebase-upload / 17125349975

21 Aug 2025 11:17AM UTC coverage: 83.509%. Remained the same
17125349975

push

github

talentlessguy
prepare for npm

10 of 25 branches covered (40.0%)

Branch coverage included in aggregate %.

228 of 260 relevant lines covered (87.69%)

3.47 hits per line

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

81.87
/utils.ts
1
import { FILEBASE_API_URL } from './constants.ts'
2✔
2
import aws4 from 'aws4'
2✔
3
import type { RequiredArgs } from './types.ts'
4

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

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

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

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

37
const hexEncode = (c: string) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`
×
38

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

43
export const buildQueryString = (query: QueryParameterBag): string => {
2✔
44
  const parts: string[] = []
3✔
45
  for (let key of Object.keys(query).sort()) {
3✔
46
    const value = query[key]
9✔
47
    key = escapeUri(key)
9✔
48
    if (Array.isArray(value)) {
×
49
      for (let i = 0, iLen = value.length; i < iLen; i++) {
×
50
        parts.push(`${key}=${escapeUri(value[i])}`)
×
51
      }
×
52
    } else {
×
53
      let qsEntry = key
9✔
54
      if (value || typeof value === 'string') {
×
55
        qsEntry += `=${escapeUri(value)}`
9✔
56
      }
9✔
57
      parts.push(qsEntry)
9✔
58
    }
9✔
59
  }
9✔
60

61
  return parts.join('&')
3✔
62
}
2✔
63

64
export const formatUrl = (
2✔
65
  request: Omit<IHttpRequest, 'headers' | 'method'>,
2✔
66
): string => {
67
  const { port, query } = request
3✔
68
  let { protocol, path, hostname } = request
3✔
69
  if (protocol && protocol.slice(-1) !== ':') {
×
70
    protocol += ':'
×
71
  }
×
72
  if (port) {
×
73
    hostname += `:${port}`
×
74
  }
×
75
  if (path && path.charAt(0) !== '/') {
×
76
    path = `/${path}`
×
77
  }
×
78
  let queryString = query ? buildQueryString(query) : ''
×
79
  if (queryString && queryString[0] !== '?') {
3✔
80
    queryString = `?${queryString}`
3✔
81
  }
3✔
82
  let auth = ''
3✔
83
  if (request.username != null || request.password != null) {
×
84
    const username = request.username ?? ''
×
85
    const password = request.password ?? ''
×
86
    auth = `${username}:${password}@`
×
87
  }
×
88
  let fragment = ''
3✔
89
  if (request.fragment) {
×
90
    fragment = `#${request.fragment}`
×
91
  }
×
92
  return `${protocol}//${auth}${hostname}${path}${queryString}${fragment}`
3✔
93
}
2✔
94

95
export const fromEnv = (filebaseToken: string): () => AwsCredentialIdentity => {
2✔
96
  return () => {
4✔
97
    const [accessKeyId, secretAccessKey] = atob(filebaseToken).split(':')
6✔
98
    if (!accessKeyId || !secretAccessKey) {
6✔
99
      throw new Error('Missing access key ID and secret access key')
7✔
100
    }
7✔
101
    return {
7✔
102
      accessKeyId,
7✔
103
      secretAccessKey,
7✔
104
    }
7✔
105
  }
4✔
106
}
2✔
107

108
export const createBucket = async (
2✔
109
  { bucketName, apiUrl, token }: RequiredArgs,
2✔
110
) => {
111
  let requestOptions: aws4.Request = {
3✔
112
    host: `${bucketName}.${apiUrl ?? FILEBASE_API_URL}`,
3✔
113
    region: 'us-east-1',
3✔
114
    method: 'PUT',
3✔
115
    service: 's3',
3✔
116
    headers: {
3✔
117
      'Content-Length': 0,
3✔
118
    },
3✔
119
  }
3✔
120
  requestOptions = generateFilebaseRequestOptions(token, requestOptions)
3✔
121
  return await fetch(`https://${requestOptions.host}/`, requestOptions as RequestInit)
3✔
122
    .then((res) => res.status == 200)
3✔
123
}
2✔
124

125
const sign = (key: Uint8Array | string, msg: string): Uint8Array =>
2✔
126
  new Uint8Array(createHmac('sha256', key).update(msg).digest())
2✔
127

128
function getSignatureKey(secretKey: string, date: string, region: string, service: string): Uint8Array {
4✔
129
  const kDate = sign(`AWS4${secretKey}`, date)
4✔
130
  const kRegion = sign(kDate, region)
4✔
131
  const kService = sign(kRegion, service)
4✔
132
  return sign(kService, 'aws4_request')
4✔
133
}
4✔
134

135
export function presignRequest(
2✔
136
  request: IHttpRequest,
2✔
137
  {
2✔
138
    accessKeyId,
2✔
139
    secretAccessKey,
2✔
140
    region,
2✔
141
    expiresIn = 3600,
2✔
142
  }: {
143
    accessKeyId: string
144
    secretAccessKey: string
145
    region: string
146
    expiresIn?: number
147
  },
2✔
148
): IHttpRequest {
149
  const method = request.method.toUpperCase()
4✔
150
  const host = request.hostname
4✔
151
  const now = new Date()
4✔
152
  const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, '')
4✔
153
  const dateStamp = amzDate.slice(0, 8)
4✔
154

155
  const credentialScope = `${dateStamp}/${region}/s3/aws4_request`
4✔
156
  const credential = `${accessKeyId}/${credentialScope}`
4✔
157

158
  const signedHeaders = 'host'
4✔
159
  const query: Record<string, string> = {
4✔
160
    'X-Amz-Algorithm': 'AWS4-HMAC-SHA256',
4✔
161
    'X-Amz-Credential': credential,
4✔
162
    'X-Amz-Date': amzDate,
4✔
163
    'X-Amz-Expires': String(expiresIn),
4✔
164
    'X-Amz-SignedHeaders': signedHeaders,
4✔
165
  }
4✔
166

167
  const canonicalQuery = Object.entries(query)
4✔
168
    .sort()
4✔
169
    .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
4✔
170
    .join('&')
4✔
171

172
  const canonicalRequest = [
4✔
173
    method,
4✔
174
    request.path,
4✔
175
    canonicalQuery,
4✔
176
    `host:${host}`,
4✔
177
    '',
4✔
178
    signedHeaders,
4✔
179
    'UNSIGNED-PAYLOAD',
4✔
180
  ].join('\n')
4✔
181

182
  const stringToSign = [
4✔
183
    'AWS4-HMAC-SHA256',
4✔
184
    amzDate,
4✔
185
    credentialScope,
4✔
186
    createHash('sha256').update(canonicalRequest).digest('hex'),
4✔
187
  ].join('\n')
4✔
188

189
  const signingKey = getSignatureKey(secretAccessKey, dateStamp, region, 's3')
4✔
190
  const signature = createHmac('sha256', signingKey).update(stringToSign).digest('hex')
4✔
191

192
  return {
4✔
193
    ...request,
4✔
194
    query: {
4✔
195
      ...query,
4✔
196
      'X-Amz-Signature': signature,
4✔
197
    },
4✔
198
  }
4✔
199
}
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