diff --git a/README.md b/README.md index cd7eb60..6cd85d8 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,9 @@ The schemes you can use include haxelib, github, gitlab, and http/https: Note that for github and gitlab you can specify credentials using the `--gh-credentials` and `--gl-private-token` parameters respectively. Be warned though that these credentials are then baked into the hxmls as well. Be very careful about using this option. +Note: authentication via .netrc file is supported! If present, a .netrc / ~.netrc will be used for URLs that require authentication. +See [the curl .netrc documentation](https://everything.curl.dev/usingcurl/netrc.html) for more information. + Because **haxelib** is a prominent source for stable versions, a **shortcut** is available: `lix +lib `. e.g. `lix +lib hxcpp#3.4.188` is equivalent for `lix install haxelib:hxcpp#3.4.188`. ### Removing a dependency diff --git a/package-lock.json b/package-lock.json index d14314f..884b41f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "devDependencies": { "@vercel/ncc": "^0.38.1", "graceful-fs": "^4.1.15", + "netrc": "^0.1.4", "tar": "^6.0.1", "yauzl": "github:lix-pm/yauzl" } @@ -110,6 +111,13 @@ "node": ">=10" } }, + "node_modules/netrc": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/netrc/-/netrc-0.1.4.tgz", + "integrity": "sha512-ye8AIYWQcP9MvoM1i0Z2jV0qed31Z8EWXYnyGNkiUAd+Fo8J+7uy90xTV8g/oAbhtjkY7iZbNTizQaXdKUuwpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -219,6 +227,12 @@ "integrity": "sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g==", "dev": true }, + "netrc": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/netrc/-/netrc-0.1.4.tgz", + "integrity": "sha512-ye8AIYWQcP9MvoM1i0Z2jV0qed31Z8EWXYnyGNkiUAd+Fo8J+7uy90xTV8g/oAbhtjkY7iZbNTizQaXdKUuwpQ==", + "dev": true + }, "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", diff --git a/package.json b/package.json index b4886ee..7151642 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "devDependencies": { "@vercel/ncc": "^0.38.1", "graceful-fs": "^4.1.15", + "netrc": "^0.1.4", "tar": "^6.0.1", "yauzl": "github:lix-pm/yauzl" }, diff --git a/src/lix/client/Auth.hx b/src/lix/client/Auth.hx new file mode 100644 index 0000000..5e1c2d2 --- /dev/null +++ b/src/lix/client/Auth.hx @@ -0,0 +1,71 @@ +package lix.client; + +import js.node.Buffer; +import js.node.Url; + +/** + * Authentication is opt-in - if no credentials are found, downloads proceed without auth. + * currently only auth via .netrc is available + */ +class Auth { + + static public function getHostname(url:String):Null { + try { + var parsed = Url.parse(url); + return parsed.hostname; + } catch (e:Dynamic) { + return null; + } + } + + static public function getNetrcAuth(hostname:String):Null<{login:String, password:String}> { + if (hostname == null) return null; + + try { + var netrc = js.Lib.require('netrc'); + var credentials = netrc(); + var machine = Reflect.field(credentials, hostname); + + if (machine != null) { + var login = Reflect.field(machine, 'login'); + var password = Reflect.field(machine, 'password'); + + if (login != null && password != null) { + return { + login: login, + password: password + }; + } + } + } catch (e:Dynamic) { + // .netrc not found, parse error, or netrc package not installed + } + + return null; + } + + static public function hasNetrcAuth(hostname:String):Bool { + return getNetrcAuth(hostname) != null; + } + + static public function createBasicAuthHeader(login:String, password:String):String { + var auth = '$login:$password'; + var encoded = Buffer.from(auth).toString('base64'); + return 'Basic $encoded'; + } + + static public function getAuthHeaders(url:String):Dynamic { + var headers = {}; + + var hostname = getHostname(url); + if (hostname == null) return headers; + + var netrcAuth = getNetrcAuth(hostname); + if (netrcAuth != null) { + Reflect.setField(headers, 'authorization', + createBasicAuthHeader(netrcAuth.login, netrcAuth.password)); + } + + return headers; + } +} diff --git a/src/lix/client/Download.hx b/src/lix/client/Download.hx index 82d77d8..de2c649 100644 --- a/src/lix/client/Download.hx +++ b/src/lix/client/Download.hx @@ -2,6 +2,7 @@ package lix.client; import haxe.Timer; import lix.client.uncompress.*; +import lix.client.Auth; import js.node.Buffer; import js.node.Url; import js.node.Http; @@ -289,6 +290,12 @@ class Download { options.headers = {}; options.headers['user-agent'] = Download.USER_AGENT; + // Add authentication headers from .netrc if available + var authHeaders = Auth.getAuthHeaders(url); + for (key in Reflect.fields(authHeaders)) { + Reflect.setField(options.headers, key, Reflect.field(authHeaders, key)); + } + function fail(e:js.Error) cb(Failure(tink.core.Error.withData('Failed to download $url because ${e.message}', e)));