{"record":{"id":"04313cca2de63f58","repo":"crowdsecurity/crowdsec","slug":"authenticate-watcher-s-w","errorCode":null,"errorMessage":"authenticate watcher (%s): %w","messagePattern":"authenticate watcher \\((.+?)\\): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/apiclient/client.go","lineNumber":129,"sourceCode":"\n\tclient := NewClient(&Config{\n\t\tMachineID:     login,\n\t\tPassword:      pwd,\n\t\tURL:           apiURL,\n\t\tPapiURL:       papiURL,\n\t\tVersionPrefix: \"v1\",\n\t\tUpdateScenario: func(_ context.Context) ([]string, error) {\n\t\t\treturn scenarios, nil\n\t\t},\n\t})\n\n\tauthResp, _, err := client.Auth.AuthenticateWatcher(ctx, models.WatcherAuthRequest{\n\t\tMachineID: &login,\n\t\tPassword:  &pwd,\n\t\tScenarios: scenarios,\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"authenticate watcher (%s): %w\", login, err)\n\t}\n\n\tvar expiration time.Time\n\tif err := expiration.UnmarshalText([]byte(authResp.Expire)); err != nil {\n\t\treturn fmt.Errorf(\"unable to parse jwt expiration: %w\", err)\n\t}\n\n\tclient.GetClient().Transport.(*JWTTransport).Token = authResp.Token\n\tclient.GetClient().Transport.(*JWTTransport).Expiration = expiration\n\n\tlapiClient = client\n\n\treturn nil\n}\n\nfunc GetLAPIClient() (*ApiClient, error) {\n\tif lapiClient == nil {\n\t\treturn nil, errors.New(\"client not initialized\")","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/apiclient/client.go#L111-L147","documentation":"InitLAPIClient authenticates the watcher against the LAPI with machine ID and password to obtain a JWT. When the AuthenticateWatcher call fails (any HTTP error response or transport failure), the error is wrapped with the login name so the developer knows which watcher failed. It is a client-side wrapper around a failed watcher login exchange.","triggerScenarios":"client.Auth.AuthenticateWatcher(ctx, models.WatcherAuthRequest{...}) returns a non-nil error: the machine does not exist on LAPI, the password is wrong, the LAPI is unreachable, TLS verification fails, or the server returns 4xx/5xx.","commonSituations":"Machine was deleted from LAPI ('cscli machines delete' or DB reset) while crowdsec config still references it; wrong api_url or api_key in config.yaml; password mismatch after manual registration; LAPI not running or wrong port; certificate/TLS misconfig.","solutions":["Verify the machine is registered on the LAPI host: run 'cscli machines list' there; if missing, re-register with 'cscli lapi register' or delete/recreate the credentials","Check api_url / lapi credentials in config for typos, wrong port, or stale password","Test connectivity: curl the LAPI /health endpoint from the client host; fix network/firewall/TLS issues","Inspect the inner wrapped error for the actual HTTP status or transport cause (e.g. 401 = bad password, connection refused = LAPI down)"],"exampleFix":"// before\nif err != nil {\n    return fmt.Errorf(\"authenticate watcher (%s): %w\", login, err)\n}\n// after (caller-side guard: check credentials before InitLAPIClient)\nif login == \"\" || password == \"\" {\n    return errors.New(\"empty lapi login or password in configuration\")\n}\nif err != nil {\n    return fmt.Errorf(\"authenticate watcher (%s): %w\", login, err)\n}","handlingStrategy":"try-catch","validationCode":"// verify credentials and LAPI reachability before InitLAPIClient\nif login == \"\" || password == \"\" {\n    return errors.New(\"missing lapi credentials\")\n}\nresp, err := http.Get(apiUrl + \"/health\")\nif err != nil || resp.StatusCode != 200 {\n    return fmt.Errorf(\"LAPI not reachable at %s\", apiUrl)\n}","typeGuard":"func isAuthFailure(err error) bool {\n    var apiErr *apiclient.APIError\n    if errors.As(err, &apiErr) {\n        return apiErr.StatusCode == http.StatusUnauthorized || apiErr.StatusCode == http.StatusForbidden\n    }\n    return false\n}","tryCatchPattern":"if err := apiclient.InitLAPIClient(ctx, apiUrl, papiUrl, login, pwd, scenarios); err != nil {\n    var apiErr *apiclient.APIError\n    if errors.As(err, &apiErr) && apiErr.StatusCode == 401 {\n        // re-register machine or refresh credentials\n    }\n    return fmt.Errorf(\"LAPI auth failed: %w\", err)\n}","preventionTips":["Keep lapi credentials in sync with 'cscli machines list' on the LAPI host","Run 'cscli lapi register' rather than hand-editing credentials","Monitor LAPI uptime; alert on machine deletions on the server","Test connectivity and TLS to the LAPI before starting the watcher"],"tags":["lapi","authentication","api-client"],"backgroundTag":"authentication-required","analyzedSha":"909b5157986a2b2c2163300fdaef5ed01289f7d2","analyzedAt":"2026-09-06T12:27:26.012Z","contentChangedAt":"2026-09-06T12:27:26.012Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}