| 169 | } |
| 170 | |
| 171 | function authHeaderIntoAuthContext(urlObject: URL, authenticateHeader: string): AuthContext { |
| 172 | const url = urlObject.toString(); |
| 173 | const parts = authenticateHeader.split(" "); |
| 174 | if (parts.length === 0) { |
| 175 | throw new Error(`can't retrieve WWW-Authenticate header in /v2 endpoint on registry ${url}: malformed`); |
| 176 | } |
| 177 | |
| 178 | const authType = parts[0].toLowerCase(); |
| 179 | switch (authType) { |
| 180 | case "bearer": |
| 181 | case "basic": |
| 182 | break; |
| 183 | case "none": |
| 184 | throw new Error("unsupported auth type for getting an auth context"); |
| 185 | default: |
| 186 | throw new Error(`unsupported auth type in WWW-Authenticate on registry ${url}: ${parts[0]}`); |
| 187 | } |
| 188 | |
| 189 | const variables = parts[1].split(","); |
| 190 | const authContextOptional: Partial<AuthContext> = {}; |
| 191 | variables.forEach((variable) => { |
| 192 | const firstEqual = variable.indexOf("="); |
| 193 | if (firstEqual === -1) { |
| 194 | throw new Error(`expected '=' but didn't encounter it on Auth header on registry ${url}`); |
| 195 | } |
| 196 | |
| 197 | const name = variable.slice(0, firstEqual); |
| 198 | let value = variable.slice(firstEqual + 1); |
| 199 | |
| 200 | if (value.length >= 2 && value[0] === `"` && value[value.length - 1] === `"`) { |
| 201 | value = value.slice(1, value.length - 1); |
| 202 | } |
| 203 | |
| 204 | switch (name) { |
| 205 | case "realm": |
| 206 | case "scope": |
| 207 | case "service": |
| 208 | authContextOptional[name] = value; |
| 209 | break; |
| 210 | default: |
| 211 | console.debug(`unknown auth attribute ${name} on registry ${url}`); |
| 212 | } |
| 213 | }); |
| 214 | |
| 215 | if (!authContextOptional.realm) throw new Error(`expected a realm on the auth header in repository ${url}`); |
| 216 | try { |
| 217 | const urlRealm = new URL(authContextOptional.realm); |
| 218 | // if service is not defined, define it by setting it to be the same as the realm's host. |
| 219 | // at the end service will be used in the request to know which service to hit |
| 220 | authContextOptional.service ??= urlRealm.host; |
| 221 | } catch { |
| 222 | throw new Error(`invalid url in realm in repository ${url}`); |
| 223 | } |
| 224 | |
| 225 | return { |
| 226 | authType, |
| 227 | realm: authContextOptional.realm!, |
| 228 | scope: authContextOptional.scope ?? "", |