diff --git a/changelog.md b/changelog.md index 4c910abb..5f6f08e5 100644 --- a/changelog.md +++ b/changelog.md @@ -19,6 +19,7 @@ * Add the options `envs.*.ghci{,d}.args` as env-specific extra arguments to those commands, based on env selection. * Add the options `ghci{,d}.args` as unconditional extra arguments to those commands. * Add the CLI option `--env` to override env selection for commands. +* Add support for token authentication for Hackage. # 0.9.0 diff --git a/lib/doc/prose.nix b/lib/doc/prose.nix index af76238c..998fe691 100644 --- a/lib/doc/prose.nix +++ b/lib/doc/prose.nix @@ -1650,8 +1650,7 @@ in { # Default for `publish` is `true` for central Hackage "hackage.haskell.org" = { - user = "deepspace-mining-corp"; - password = { + token = { type = "exec"; value = "/path/to/password/script"; }; @@ -1676,8 +1675,8 @@ in { } ``` - With this config, the app will execute the configured script to obtain the password for central Hackage, and fetch - that for `prod` from the given environment variable. + With this config, the app will execute the configured script to obtain the auth token for central Hackage, and fetch + the password for `prod` from the given environment variable. For `staging`, the credentials must be specified as CLI args: ``` diff --git a/modules/hackage-repo.nix b/modules/hackage-repo.nix index d0bef9cf..9990ecf0 100644 --- a/modules/hackage-repo.nix +++ b/modules/hackage-repo.nix @@ -48,6 +48,12 @@ in { default = null; }; + token = util.maybeOption types.str { + description = '' + Authentication token for uploading. + ''; + }; + secure = lib.mkOption { description = "Use the newer Cabal client that verifies index signatures via `hackage-security`."; type = types.nullOr types.bool; diff --git a/packages/hix/lib/Hix/Managed/Cabal/Config.hs b/packages/hix/lib/Hix/Managed/Cabal/Config.hs index dd44429e..0529bc5c 100644 --- a/packages/hix/lib/Hix/Managed/Cabal/Config.hs +++ b/packages/hix/lib/Hix/Managed/Cabal/Config.hs @@ -28,9 +28,18 @@ import Hix.Managed.Cabal.Data.ContextHackageRepo ( ContextHackageLocation (..), ContextHackagePassword (..), ContextHackageRepo (..), + ContextHackageSecret (..), + ContextHackageToken (..), ) import qualified Hix.Managed.Cabal.Data.HackageLocation as HackageLocation -import Hix.Managed.Cabal.Data.HackageLocation (HackageLocation (auth), HackagePassword (HackagePassword), HackageUser) +import Hix.Managed.Cabal.Data.HackageLocation ( + HackageAuth (..), + HackageLocation (auth), + HackagePassword (..), + HackageSecret (..), + HackageToken (..), + HackageUser, + ) import Hix.Managed.Cabal.Data.HackageRepo (HackageName, HackageRepo (..), centralName) import Hix.Managed.Cabal.HackageLocation (parseLocation) import Hix.Managed.Cabal.HackageRepo (hackageDescription) @@ -64,18 +73,18 @@ isReinstallableId package = isReinstallable package.name isNonReinstallableDep :: MutableDep -> Bool isNonReinstallableDep = isNonReinstallable . depName -resolvePasswordEnvVar :: Text -> M HackagePassword -resolvePasswordEnvVar name = +resolveSecretEnvVar :: Text -> M HackageSecret +resolveSecretEnvVar name = lookup >>= \case [] -> clientError (message "is empty") - value -> pure (HackagePassword (toText value)) + value -> pure (HackageSecret (toText value)) where lookup = noteClient (message "does not exist") =<< tryIOM (lookupEnv (toString name)) message problem = [exon|The specified environment variable #{Color.cyan name} #{problem}|] -resolvePasswordExec :: Text -> M HackagePassword -resolvePasswordExec spec = +resolveSecretExec :: Text -> M HackageSecret +resolveSecretExec spec = noteClient (message "is not a valid path") (Path.parseSomeFile (toString spec)) >>= \case Abs path -> checkPath path Rel rel -> @@ -93,7 +102,7 @@ resolvePasswordExec spec = liftIO (tryIOError (Process.readProcessStdout (Process.proc (toFilePath path) []))) >>= \case Right (ExitSuccess, output) | LByteString.null output -> failure "printed nothing on stdout" - | [pw] <- Text.lines (decodeUtf8 output) -> pure (HackagePassword pw) + | [secret] <- Text.lines (decodeUtf8 output) -> pure (HackageSecret secret) | otherwise -> failure "printed multiple lines" Right (ExitFailure code, _) -> failure [exon|exited with code #{show @_ @Int code}|] Left err -> do @@ -104,13 +113,13 @@ resolvePasswordExec spec = message problem = [exon|The specified executable #{Color.path spec} #{problem}|] -resolvePassword :: ContextHackagePassword -> M HackagePassword -resolvePassword = +resolveSecret :: ContextHackageSecret -> M HackageSecret +resolveSecret = appContextVerbose ctx . \case - PasswordUnobscured pw -> pure pw - PasswordPlain pw -> pure pw - PasswordEnvVar name -> resolvePasswordEnvVar name - PasswordExec path -> resolvePasswordExec path + SecretUnobscured secret -> pure secret + SecretPlain secret -> pure secret + SecretEnvVar name -> resolveSecretEnvVar name + SecretExec path -> resolveSecretExec path where ctx = "resolving the password" @@ -118,26 +127,34 @@ withAuth :: HackageLocation -> Maybe HackageUser -> Maybe ContextHackagePassword -> + Maybe ContextHackageToken -> M HackageLocation withAuth location = \cases - (Just user) Nothing -> + _ (Just _) (Just _) -> + bothSorts + (Just user) Nothing _ -> onlyOne [exon|user (##{user})|] "password" - Nothing (Just _) -> + Nothing (Just _) Nothing -> onlyOne "password" "user" - Nothing Nothing -> + Nothing Nothing Nothing -> pure location - (Just user) (Just passwordSpec) -> do - password <- resolvePassword passwordSpec - pure location {auth = Just (user, password)} + (Just user) (Just passwordSpec) Nothing -> do + secret <- resolveSecret passwordSpec.secret + pure location {auth = Just (HackageAuthPassword {user, password = HackagePassword secret})} + Nothing Nothing (Just tokenSpec) -> do + secret <- resolveSecret tokenSpec.secret + pure location {auth = Just (HackageAuthToken {token = HackageToken secret})} where onlyOne present absent = clientError [exon|Specified a #{present}, but no #{absent}|] + bothSorts = clientError "Specified both password and auth token" + validateContextRepo :: ContextHackageRepo -> M HackageRepo validateContextRepo ContextHackageRepo {location = location0, ..} = do appContextVerbose [exon|validating the Hackage config #{Color.yellow name}|] do location1 <- for location0 \ (ContextHackageLocation spec) -> eitherClient (first toText (parseLocation (toString spec))) - location <- withAuth (fromMaybe HackageLocation.central location1) user password + location <- withAuth (fromMaybe HackageLocation.central location1) user password token pure HackageRepo { name, description = fromMaybe (hackageDescription location) description, diff --git a/packages/hix/lib/Hix/Managed/Cabal/ContextHackageRepo.hs b/packages/hix/lib/Hix/Managed/Cabal/ContextHackageRepo.hs index 55fe4705..86398740 100644 --- a/packages/hix/lib/Hix/Managed/Cabal/ContextHackageRepo.hs +++ b/packages/hix/lib/Hix/Managed/Cabal/ContextHackageRepo.hs @@ -14,9 +14,11 @@ import Hix.Managed.Cabal.Data.ContextHackageRepo ( ContextHackageLocation (..), ContextHackagePassword (..), ContextHackageRepo (..), + ContextHackageSecret (..), + ContextHackageToken (..), contextHackageRepo, ) -import Hix.Managed.Cabal.Data.HackageLocation (HackagePassword (..), HackageUser (..)) +import Hix.Managed.Cabal.Data.HackageLocation (HackageSecret (..), HackageUser (..)) import Hix.Managed.Cabal.Data.HackageRepo (HackageName, centralName) update' :: @@ -57,7 +59,8 @@ fields = ("enable", update #enable bool), ("location", update #location (text ContextHackageLocation)), ("user", update #user (text HackageUser)), - ("password", update' True #password (text (PasswordPlain . HackagePassword))), + ("password", update' True #password (text (ContextHackagePassword . SecretPlain . HackageSecret))), + ("token", update' True #token (text (ContextHackageToken . SecretPlain . HackageSecret))), ("secure", update #secure bool), ("keys", update #keys (nonEmpty . Text.splitOn "," . toText)), ("indexState", update #indexState simpleParsec), diff --git a/packages/hix/lib/Hix/Managed/Cabal/Data/ContextHackageRepo.hs b/packages/hix/lib/Hix/Managed/Cabal/Data/ContextHackageRepo.hs index ab709fb3..0bb7301e 100644 --- a/packages/hix/lib/Hix/Managed/Cabal/Data/ContextHackageRepo.hs +++ b/packages/hix/lib/Hix/Managed/Cabal/Data/ContextHackageRepo.hs @@ -7,7 +7,7 @@ import Text.PrettyPrint import Hix.Class.EncodeNix (EncodeNix (encodeNix)) import Hix.Data.NixExpr (Expr (..), ExprAttr (..)) -import Hix.Managed.Cabal.Data.HackageLocation (HackagePassword (HackagePassword), HackageUser) +import Hix.Managed.Cabal.Data.HackageLocation (HackageSecret (..), HackageUser) import Hix.Managed.Cabal.Data.HackageRepo (HackageDescription, HackageIndexState, HackageName) import Hix.NixExpr (mkAttrs, single, singleOpt) import Hix.Pretty (field, prettyFieldsV, prettyText) @@ -20,30 +20,30 @@ newtype ContextHackageLocation = instance Pretty ContextHackageLocation where pretty = prettyText . coerce -data ContextHackagePassword = +data ContextHackageSecret = -- | Password was intended to be printed, most likely in a test. - PasswordUnobscured HackagePassword + SecretUnobscured HackageSecret | - PasswordPlain HackagePassword + SecretPlain HackageSecret | - PasswordEnvVar Text + SecretEnvVar Text | - PasswordExec Text + SecretExec Text deriving stock (Eq, Show) -instance Pretty ContextHackagePassword where +instance Pretty ContextHackageSecret where pretty = \case - PasswordUnobscured (HackagePassword pw) -> prettyText pw - PasswordPlain _ -> "" - PasswordEnvVar var -> prettyText var <+> brackets (text "env-var") - PasswordExec exe -> prettyText exe <+> brackets (text "exec") + SecretUnobscured (HackageSecret pw) -> prettyText pw + SecretPlain _ -> "" + SecretEnvVar var -> prettyText var <+> brackets (text "env-var") + SecretExec exe -> prettyText exe <+> brackets (text "exec") -instance EncodeNix ContextHackagePassword where +instance EncodeNix ContextHackageSecret where encodeNix = \case - PasswordUnobscured (HackagePassword pw) -> ExprString pw - PasswordPlain _ -> ExprString "" - PasswordEnvVar var -> structured "env-var" var - PasswordExec exe -> structured "exec" exe + SecretUnobscured (HackageSecret pw) -> ExprString pw + SecretPlain _ -> ExprString "" + SecretEnvVar var -> structured "env-var" var + SecretExec exe -> structured "exec" exe where structured t value = ExprAttrs [ @@ -51,21 +51,31 @@ instance EncodeNix ContextHackagePassword where ExprAttr {name = "value", value = ExprString value} ] -instance FromJSON ContextHackagePassword where +instance FromJSON ContextHackageSecret where parseJSON v = - withText "ContextHackagePassword" plain v + withText "ContextHackageSecret" plain v <|> - withObject "ContextHackagePassword" typed v + withObject "ContextHackageSecret" typed v where typed o = do value <- o .: "value" o .: "type" >>= \case ("plain" :: Text) -> plain value - "env-var" -> pure (PasswordEnvVar value) - "exec" -> pure (PasswordExec value) + "env-var" -> pure (SecretEnvVar value) + "exec" -> pure (SecretExec value) t -> fail [exon|Invalid value for Hackage password type: ##{t}|] - plain = pure . PasswordPlain . HackagePassword + plain = pure . SecretPlain . HackageSecret + +newtype ContextHackagePassword = + ContextHackagePassword { secret :: ContextHackageSecret } + deriving stock (Eq, Show, Generic) + deriving newtype (Pretty, EncodeNix, FromJSON) + +newtype ContextHackageToken = + ContextHackageToken { secret :: ContextHackageSecret } + deriving stock (Eq, Show, Generic) + deriving newtype (Pretty, EncodeNix, FromJSON) data ContextHackageRepo = ContextHackageRepo { @@ -75,6 +85,7 @@ data ContextHackageRepo = location :: Maybe ContextHackageLocation, user :: Maybe HackageUser, password :: Maybe ContextHackagePassword, + token :: Maybe ContextHackageToken, secure :: Maybe Bool, keys :: Maybe (NonEmpty Text), indexState :: Maybe HackageIndexState, @@ -93,6 +104,7 @@ instance Pretty ContextHackageRepo where field "location" location, field "user" user, field "password" password, + field "token" token, field "secure" secure, field "keys" keys, field "indexState" indexState, @@ -125,6 +137,7 @@ contextHackageRepo name = location = Nothing, user = Nothing, password = Nothing, + token = Nothing, secure = Nothing, keys = Nothing, indexState = Nothing, diff --git a/packages/hix/lib/Hix/Managed/Cabal/Data/HackageLocation.hs b/packages/hix/lib/Hix/Managed/Cabal/Data/HackageLocation.hs index d008d5a1..6418d9be 100644 --- a/packages/hix/lib/Hix/Managed/Cabal/Data/HackageLocation.hs +++ b/packages/hix/lib/Hix/Managed/Cabal/Data/HackageLocation.hs @@ -60,23 +60,39 @@ newtype HackageUser = instance Pretty HackageUser where pretty = prettyNt -newtype HackagePassword = - HackagePassword Text +newtype HackageSecret = + HackageSecret { text :: Text } deriving stock (Eq) deriving newtype (IsString, Ord, FromJSON) -instance Show HackagePassword where +instance Show HackageSecret where showsPrec d _ = showParen (d > 10) (showString "HackagePassword ") -instance Pretty HackagePassword where +instance Pretty HackageSecret where pretty _ = "" +newtype HackagePassword = + HackagePassword { secret :: HackageSecret } + deriving stock (Eq, Show) + deriving newtype (IsString, Ord, FromJSON) + +newtype HackageToken = + HackageToken { secret :: HackageSecret } + deriving stock (Eq, Show) + deriving newtype (IsString, Ord, FromJSON) + +data HackageAuth = + HackageAuthPassword { user :: HackageUser, password :: HackagePassword } + | + HackageAuthToken { token :: HackageToken } + deriving stock (Eq, Show) + data HackageLocation = HackageLocation { host :: HackageHost, tls :: HackageTls, port :: Maybe HackagePort, - auth :: Maybe (HackageUser, HackagePassword) + auth :: Maybe HackageAuth } deriving stock (Eq, Show, Generic) diff --git a/packages/hix/lib/Hix/Managed/Handlers/HackageClient/Prod.hs b/packages/hix/lib/Hix/Managed/Handlers/HackageClient/Prod.hs index 263eb14b..cf722e5b 100644 --- a/packages/hix/lib/Hix/Managed/Handlers/HackageClient/Prod.hs +++ b/packages/hix/lib/Hix/Managed/Handlers/HackageClient/Prod.hs @@ -18,6 +18,7 @@ import Network.HTTP.Client.MultipartFormData (formDataBody, partBS) import Network.HTTP.Types ( Status (statusCode, statusMessage), hAccept, + hAuthorization, hContentType, statusIsClientError, statusIsServerError, @@ -31,10 +32,13 @@ import Hix.Http (httpManager) import qualified Hix.Log as Log import Hix.Managed.Cabal.Data.Config (CabalConfig, HackagePurpose, hackagesFor) import Hix.Managed.Cabal.Data.HackageLocation ( + HackageAuth (..), HackageHost (..), HackageLocation (..), - HackagePassword (HackagePassword), + HackagePassword (..), + HackageSecret (..), HackageTls (..), + HackageToken (..), HackageUser (..), hackageTlsBool, ) @@ -101,8 +105,11 @@ nativeRequest location request@HackageRequest {..} = do Left fields -> formDataBody [partBS key (encodeUtf8 value) | (key, value) <- toList fields] addAuth = - maybe id \ (HackageUser user, HackagePassword password) -> - applyBasicAuth (encodeUtf8 user) (encodeUtf8 password) + maybe id \case + HackageAuthPassword {user = HackageUser user, password = HackagePassword (HackageSecret password)} -> + applyBasicAuth (encodeUtf8 user) (encodeUtf8 password) + HackageAuthToken {token = HackageToken (HackageSecret token)} -> + \ req -> req {requestHeaders = (hAuthorization, (encodeUtf8 token)) : req.requestHeaders} addQuery = maybe id \ q -> setQueryString (second Just <$> toList q) @@ -178,8 +185,8 @@ handlersMock manager port = do host = "localhost", tls = TlsOff, port = Just (fromIntegral port), - auth = Just ("admin", "admin") + auth = Just (HackageAuthPassword {user = "admin", password = "admin"}) } } - userRes = res {location = res.location {auth = Just ("test", "test")}} + userRes = res {location = res.location {auth = Just (HackageAuthPassword {user = "test", password = "test"})}} adminClient = handlersProd res diff --git a/packages/hix/test/Hix/Test/Managed/HackageTest.hs b/packages/hix/test/Hix/Test/Managed/HackageTest.hs index c71f7c3a..d704e019 100644 --- a/packages/hix/test/Hix/Test/Managed/HackageTest.hs +++ b/packages/hix/test/Hix/Test/Managed/HackageTest.hs @@ -10,6 +10,7 @@ import Hix.Managed.Cabal.Data.ContextHackageRepo ( ContextHackageLocation (..), ContextHackagePassword (..), ContextHackageRepo (..), + ContextHackageSecret (..), ) import Hix.Managed.Cabal.Data.HackageLocation (HackageLocation (..), HackageTls (TlsOff), hackageLocation) import Hix.Managed.Cabal.HackageLocation (noSchemeMessage, parseLocation) @@ -49,7 +50,8 @@ contextRepo = enable = Just False, location = Just (ContextHackageLocation "http://localhost:1234"), user = Just "test", - password = Just (PasswordUnobscured "test"), + password = Just (ContextHackagePassword (SecretUnobscured "test")), + token = Nothing, secure = Just False, keys = Just ["key1", "key2"], indexState = Just (unsafeParsec ("2024-01-01T00:00:00Z" :: String)), diff --git a/packages/integration/lib/Hix/Integration/Hackage.hs b/packages/integration/lib/Hix/Integration/Hackage.hs index b0b769d8..e27169c4 100644 --- a/packages/integration/lib/Hix/Integration/Hackage.hs +++ b/packages/integration/lib/Hix/Integration/Hackage.hs @@ -31,9 +31,10 @@ import Hix.Managed.Cabal.Data.ContextHackageRepo ( ContextHackageLocation (..), ContextHackagePassword (..), ContextHackageRepo (..), + ContextHackageSecret (..), contextHackageRepo, ) -import Hix.Managed.Cabal.Data.HackageLocation (HackageLocation (..), HackageTls (..)) +import Hix.Managed.Cabal.Data.HackageLocation (HackageAuth (..), HackageLocation (..), HackageTls (..)) import Hix.Managed.Cabal.Data.HackageRepo (HackageRepo (..)) import qualified Hix.Managed.Cabal.Init as Cabal import Hix.Managed.Cabal.Init (remoteRepo) @@ -105,7 +106,7 @@ testRepo port = location = HackageLocation { host = "localhost", port = Just (fromIntegral port.value), - auth = Just ("test", "test"), + auth = Just (HackageAuthPassword {user = "test", password = "test"}), tls = TlsOff }, enable = True, @@ -129,7 +130,7 @@ testContextRepo port = description = Just "test", location = Just (ContextHackageLocation [exon|http://localhost:#{show port.value}|]), user = Just "test", - password = Just (PasswordUnobscured "test"), + password = Just (ContextHackagePassword (SecretUnobscured "test")), solver = Just True, publish = Just True } @@ -160,7 +161,9 @@ testClient location = do location } - adminRes = res {HackageResources.location = location {auth = Just ("admin", "admin")}} + adminRes = res {HackageResources.location = location { + auth = Just (HackageAuthPassword {user = "admin", password = "admin"})} + } adminClient = HackageClient.handlersProd adminRes diff --git a/packages/integration/test/Hix/Integration/HackageTest.hs b/packages/integration/test/Hix/Integration/HackageTest.hs index be5b249e..e32d28af 100644 --- a/packages/integration/test/Hix/Integration/HackageTest.hs +++ b/packages/integration/test/Hix/Integration/HackageTest.hs @@ -13,7 +13,7 @@ import Hix.Hackage (hackagePostQuery, hackagePut) import Hix.Http (httpManager) import Hix.Integration.Hackage (withHackage) import Hix.Integration.Utils (UnitTest, addFile, runMTest) -import Hix.Managed.Cabal.Data.HackageLocation (HackageLocation (..), HackageTls (TlsOff)) +import Hix.Managed.Cabal.Data.HackageLocation (HackageAuth (..), HackageLocation (..), HackageTls (TlsOff)) import Hix.Managed.Cabal.Data.HackageRepo (HackageRepo (secure)) import Hix.Managed.Cabal.HackageRepo (hackageRepo) import qualified Hix.Managed.Cabal.Init as Cabal @@ -95,7 +95,7 @@ testServer :: testServer port = do manager <- httpManager let - auth = ("admin", "admin") + auth = (HackageAuthPassword {user = "admin", password = "admin"}) res = HackageResources { description = "test", manager, @@ -106,7 +106,7 @@ testServer port = do tls = TlsOff } } - userRes = res {location = res.location {auth = Just ("test", "test")}} + userRes = res {location = res.location {auth = Just (HackageAuthPassword {user = "test", password = "test"})}} client = HackageClient.handlersProd res userClient = HackageClient.handlersProd userRes repo = (hackageRepo "test" res.location) {secure = Nothing} diff --git a/packages/integration/test/Hix/Integration/ReleaseMaintenanceTest.hs b/packages/integration/test/Hix/Integration/ReleaseMaintenanceTest.hs index 747e2916..6d08682d 100644 --- a/packages/integration/test/Hix/Integration/ReleaseMaintenanceTest.hs +++ b/packages/integration/test/Hix/Integration/ReleaseMaintenanceTest.hs @@ -23,7 +23,7 @@ import Hix.Integration.Hackage (withHackage) import Hix.Integration.Utils (UnitTest, add, addP, libHs, local1, runMTest, withHixDir) import Hix.Managed.Cabal.ContextHackageRepo (unsafeCentralHackageContextFixed) import Hix.Managed.Cabal.Data.Config (GhcDb (GhcDbSystem)) -import Hix.Managed.Cabal.Data.HackageLocation (HackageLocation (..), HackageTls (TlsOff)) +import Hix.Managed.Cabal.Data.HackageLocation (HackageAuth (..), HackageLocation (..), HackageTls (TlsOff)) import Hix.Managed.Cabal.Data.HackageRepo (HackageRepo (..)) import Hix.Managed.Cabal.Data.Revision (Revision (..)) import qualified Hix.Managed.Cabal.Init as Cabal @@ -278,7 +278,7 @@ testRepo port = location = HackageLocation { host = "localhost", port = Just (fromIntegral port.value), - auth = Just ("test", "test"), + auth = Just (HackageAuthPassword {user = "test", password = "test"}), tls = TlsOff }, enable = True, diff --git a/packages/integration/test/Hix/Integration/RevisionTest.hs b/packages/integration/test/Hix/Integration/RevisionTest.hs index ace41de2..acdc89d8 100644 --- a/packages/integration/test/Hix/Integration/RevisionTest.hs +++ b/packages/integration/test/Hix/Integration/RevisionTest.hs @@ -18,7 +18,7 @@ import Hix.Error (pathText) import Hix.Integration.Hackage (TestHackage (..), withHackageClient) import Hix.Integration.Utils (UnitTest, add, addP, libHs, local1, runMTest, withHixDir) import Hix.Managed.Cabal.Data.Config (GhcDb (GhcDbSystem)) -import Hix.Managed.Cabal.Data.HackageLocation (HackageLocation (..), HackageTls (TlsOff)) +import Hix.Managed.Cabal.Data.HackageLocation (HackageAuth (..), HackageLocation (..), HackageTls (TlsOff)) import Hix.Managed.Cabal.Data.HackageRepo (HackageRepo (..)) import Hix.Managed.Cabal.Data.Revision (Revision (..)) import qualified Hix.Managed.Cabal.Init as Cabal @@ -278,7 +278,7 @@ testRepo port = location = HackageLocation { host = "localhost", port = Just (fromIntegral port.value), - auth = Just ("test", "test"), + auth = Just (HackageAuthPassword {user = "test", password = "test"}), tls = TlsOff }, enable = True,