diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index dd09423d..6e183f5b 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -762,6 +762,8 @@ func displayDNSHelp(name string) error { ew.writeln(`Credentials:`) ew.writeln(` - "JOKER_API_KEY": API key`) + ew.writeln(` - "JOKER_PASSWORD": Joker.com password`) + ew.writeln(` - "JOKER_USERNAME": Joker.com username (email address)`) ew.writeln() ew.writeln(`Additional Configuration:`) diff --git a/docs/content/dns/zz_gen_joker.md b/docs/content/dns/zz_gen_joker.md index f41ffb5a..19476320 100644 --- a/docs/content/dns/zz_gen_joker.md +++ b/docs/content/dns/zz_gen_joker.md @@ -21,6 +21,10 @@ Configuration for [Joker](https://joker.com). Here is an example bash command using the Joker provider: ```bash +JOKER_USERNAME= \ +JOKER_PASSWORD= \ +lego --dns joker --domains my.domain.com --email my@email.com run +# or JOKER_API_KEY= \ lego --dns joker --domains my.domain.com --email my@email.com run ``` @@ -33,6 +37,8 @@ lego --dns joker --domains my.domain.com --email my@email.com run | Environment Variable Name | Description | |-----------------------|-------------| | `JOKER_API_KEY` | API key | +| `JOKER_PASSWORD` | Joker.com password | +| `JOKER_USERNAME` | Joker.com username (email address) | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. More information [here](/lego/dns/#configuration-and-credentials). diff --git a/providers/dns/joker/client.go b/providers/dns/joker/client.go index 18265a85..8e8fabda 100644 --- a/providers/dns/joker/client.go +++ b/providers/dns/joker/client.go @@ -70,7 +70,20 @@ func (d *DNSProvider) login() (*response, error) { return nil, nil } - response, err := d.postRequest("login", url.Values{"api-key": {d.config.APIKey}}) + var values url.Values + switch { + case d.config.Username != "" && d.config.Password != "": + values = url.Values{ + "username": {d.config.Username}, + "password": {d.config.Password}, + } + case d.config.APIKey != "": + values = url.Values{"api-key": {d.config.APIKey}} + default: + return nil, fmt.Errorf("no username and password or api-key") + } + + response, err := d.postRequest("login", values) if err != nil { return response, err } diff --git a/providers/dns/joker/client_test.go b/providers/dns/joker/client_test.go index e0ead7b5..13e6ec4b 100644 --- a/providers/dns/joker/client_test.go +++ b/providers/dns/joker/client_test.go @@ -12,9 +12,15 @@ import ( ) const ( - correctAuth = "123" - incorrectAuth = "321" - serverErrorAuth = "500" + correctAPIKey = "123" + incorrectAPIKey = "321" + serverErrorAPIKey = "500" +) + +const ( + correctUsername = "lego" + incorrectUsername = "not_lego" + serverErrorUsername = "error" ) func setup() (*http.ServeMux, *httptest.Server) { @@ -23,35 +29,35 @@ func setup() (*http.ServeMux, *httptest.Server) { return mux, server } -func TestDNSProvider_login(t *testing.T) { +func TestDNSProvider_login_api_key(t *testing.T) { testCases := []struct { desc string - authKey string + apiKey string expectedError bool expectedStatusCode int expectedAuthSid string }{ { desc: "correct key", - authKey: correctAuth, + apiKey: correctAPIKey, expectedStatusCode: 0, - expectedAuthSid: correctAuth, + expectedAuthSid: correctAPIKey, }, { desc: "incorrect key", - authKey: incorrectAuth, + apiKey: incorrectAPIKey, expectedStatusCode: 2200, expectedError: true, }, { desc: "server error", - authKey: serverErrorAuth, + apiKey: serverErrorAPIKey, expectedStatusCode: -500, expectedError: true, }, { desc: "non-ok status code", - authKey: "333", + apiKey: "333", expectedStatusCode: 2202, expectedError: true, }, @@ -64,11 +70,11 @@ func TestDNSProvider_login(t *testing.T) { require.Equal(t, "POST", r.Method) switch r.FormValue("api-key") { - case correctAuth: + case correctAPIKey: _, _ = io.WriteString(w, "Status-Code: 0\nStatus-Text: OK\nAuth-Sid: 123\n\ncom\nnet") - case incorrectAuth: + case incorrectAPIKey: _, _ = io.WriteString(w, "Status-Code: 2200\nStatus-Text: Authentication error") - case serverErrorAuth: + case serverErrorAPIKey: http.NotFound(w, r) default: _, _ = io.WriteString(w, "Status-Code: 2202\nStatus-Text: OK\n\ncom\nnet") @@ -79,7 +85,89 @@ func TestDNSProvider_login(t *testing.T) { t.Run(test.desc, func(t *testing.T) { config := NewDefaultConfig() config.BaseURL = server.URL - config.APIKey = test.authKey + config.APIKey = test.apiKey + + p, err := NewDNSProviderConfig(config) + require.NoError(t, err) + require.NotNil(t, p) + + response, err := p.login() + if test.expectedError { + require.Error(t, err) + } else { + require.NoError(t, err) + require.NotNil(t, response) + assert.Equal(t, test.expectedStatusCode, response.StatusCode) + assert.Equal(t, test.expectedAuthSid, response.AuthSid) + } + }) + } +} + +func TestDNSProvider_login_username(t *testing.T) { + testCases := []struct { + desc string + username string + password string + expectedError bool + expectedStatusCode int + expectedAuthSid string + }{ + { + desc: "correct username and password", + username: correctUsername, + password: "go-acme", + expectedError: false, + expectedStatusCode: 0, + expectedAuthSid: correctAPIKey, + }, + { + desc: "incorrect username", + username: incorrectUsername, + password: "go-acme", + expectedStatusCode: 2200, + expectedError: true, + }, + { + desc: "server error", + username: serverErrorUsername, + password: "go-acme", + expectedStatusCode: -500, + expectedError: true, + }, + { + desc: "non-ok status code", + username: "random", + password: "go-acme", + expectedStatusCode: 2202, + expectedError: true, + }, + } + + mux, server := setup() + defer server.Close() + + mux.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, "POST", r.Method) + + switch r.FormValue("username") { + case correctUsername: + _, _ = io.WriteString(w, "Status-Code: 0\nStatus-Text: OK\nAuth-Sid: 123\n\ncom\nnet") + case incorrectUsername: + _, _ = io.WriteString(w, "Status-Code: 2200\nStatus-Text: Authentication error") + case serverErrorUsername: + http.NotFound(w, r) + default: + _, _ = io.WriteString(w, "Status-Code: 2202\nStatus-Text: OK\n\ncom\nnet") + } + }) + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.BaseURL = server.URL + config.Username = test.username + config.Password = test.password p, err := NewDNSProviderConfig(config) require.NoError(t, err) @@ -107,12 +195,12 @@ func TestDNSProvider_logout(t *testing.T) { }{ { desc: "correct auth-sid", - authSid: correctAuth, + authSid: correctAPIKey, expectedStatusCode: 0, }, { desc: "incorrect auth-sid", - authSid: incorrectAuth, + authSid: incorrectAPIKey, expectedStatusCode: 2200, }, { @@ -122,7 +210,7 @@ func TestDNSProvider_logout(t *testing.T) { }, { desc: "server error", - authSid: serverErrorAuth, + authSid: serverErrorAPIKey, expectedError: true, }, } @@ -134,9 +222,9 @@ func TestDNSProvider_logout(t *testing.T) { require.Equal(t, "POST", r.Method) switch r.FormValue("auth-sid") { - case correctAuth: + case correctAPIKey: _, _ = io.WriteString(w, "Status-Code: 0\nStatus-Text: OK\n") - case incorrectAuth: + case incorrectAPIKey: _, _ = io.WriteString(w, "Status-Code: 2200\nStatus-Text: Authentication error") default: http.NotFound(w, r) @@ -179,20 +267,20 @@ func TestDNSProvider_getZone(t *testing.T) { }{ { desc: "correct auth-sid, known domain", - authSid: correctAuth, + authSid: correctAPIKey, domain: "known", zone: testZone, expectedStatusCode: 0, }, { desc: "incorrect auth-sid, known domain", - authSid: incorrectAuth, + authSid: incorrectAPIKey, domain: "known", expectedStatusCode: 2202, }, { desc: "correct auth-sid, unknown domain", - authSid: correctAuth, + authSid: correctAPIKey, domain: "unknown", expectedStatusCode: 2202, }, @@ -213,9 +301,9 @@ func TestDNSProvider_getZone(t *testing.T) { domain := r.FormValue("domain") switch { - case authSid == correctAuth && domain == "known": + case authSid == correctAPIKey && domain == "known": _, _ = io.WriteString(w, "Status-Code: 0\nStatus-Text: OK\n\n"+testZone) - case authSid == incorrectAuth || (authSid == correctAuth && domain == "unknown"): + case authSid == incorrectAPIKey || (authSid == correctAPIKey && domain == "unknown"): _, _ = io.WriteString(w, "Status-Code: 2202\nStatus-Text: Authorization error") default: http.NotFound(w, r) diff --git a/providers/dns/joker/joker.go b/providers/dns/joker/joker.go index d2dc1cac..c39fb9ed 100644 --- a/providers/dns/joker/joker.go +++ b/providers/dns/joker/joker.go @@ -18,6 +18,8 @@ type Config struct { Debug bool BaseURL string APIKey string + Username string + Password string PropagationTimeout time.Duration PollingInterval time.Duration TTL int @@ -50,11 +52,17 @@ type DNSProvider struct { func NewDNSProvider() (*DNSProvider, error) { values, err := env.Get("JOKER_API_KEY") if err != nil { - return nil, fmt.Errorf("joker: %v", err) + var errU error + values, errU = env.Get("JOKER_USERNAME", "JOKER_PASSWORD") + if errU != nil { + return nil, fmt.Errorf("joker: %v or %v", errU, err) + } } config := NewDefaultConfig() config.APIKey = values["JOKER_API_KEY"] + config.Username = values["JOKER_USERNAME"] + config.Password = values["JOKER_PASSWORD"] return NewDNSProviderConfig(config) } @@ -66,7 +74,9 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } if config.APIKey == "" { - return nil, fmt.Errorf("joker: credentials missing") + if config.Username == "" || config.Password == "" { + return nil, fmt.Errorf("joker: credentials missing") + } } if !strings.HasSuffix(config.BaseURL, "/") { diff --git a/providers/dns/joker/joker.toml b/providers/dns/joker/joker.toml index 0cdace4d..53a2b811 100644 --- a/providers/dns/joker/joker.toml +++ b/providers/dns/joker/joker.toml @@ -5,6 +5,10 @@ Code = "joker" Since = "v2.6.0" Example = ''' +JOKER_USERNAME= \ +JOKER_PASSWORD= \ +lego --dns joker --domains my.domain.com --email my@email.com run +# or JOKER_API_KEY= \ lego --dns joker --domains my.domain.com --email my@email.com run ''' @@ -12,6 +16,8 @@ lego --dns joker --domains my.domain.com --email my@email.com run [Configuration] [Configuration.Credentials] JOKER_API_KEY = "API key" + JOKER_USERNAME = "Joker.com username (email address)" + JOKER_PASSWORD = "Joker.com password" [Configuration.Additional] JOKER_POLLING_INTERVAL = "Time between DNS propagation check" JOKER_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" diff --git a/providers/dns/joker/joker_test.go b/providers/dns/joker/joker_test.go index 427ac29d..c672ef81 100644 --- a/providers/dns/joker/joker_test.go +++ b/providers/dns/joker/joker_test.go @@ -9,7 +9,8 @@ import ( "github.com/stretchr/testify/require" ) -var envTest = tester.NewEnvTest("JOKER_API_KEY").WithDomain("JOKER_DOMAIN") +var envTest = tester.NewEnvTest("JOKER_API_KEY", "JOKER_USERNAME", "JOKER_PASSWORD"). + WithDomain("JOKER_DOMAIN") func TestNewDNSProvider(t *testing.T) { testCases := []struct { @@ -18,17 +19,44 @@ func TestNewDNSProvider(t *testing.T) { expected string }{ { - desc: "success", + desc: "success API key", envVars: map[string]string{ "JOKER_API_KEY": "123", }, }, { - desc: "missing key", + desc: "success username password", envVars: map[string]string{ - "JOKER_API_KEY": "", + "JOKER_USERNAME": "123", + "JOKER_PASSWORD": "123", }, - expected: "joker: some credentials information are missing: JOKER_API_KEY", + }, + { + desc: "missing credentials", + envVars: map[string]string{ + "JOKER_API_KEY": "", + "JOKER_USERNAME": "", + "JOKER_PASSWORD": "", + }, + expected: "joker: some credentials information are missing: JOKER_USERNAME,JOKER_PASSWORD or some credentials information are missing: JOKER_API_KEY", + }, + { + desc: "missing password", + envVars: map[string]string{ + "JOKER_API_KEY": "", + "JOKER_USERNAME": "123", + "JOKER_PASSWORD": "", + }, + expected: "joker: some credentials information are missing: JOKER_PASSWORD or some credentials information are missing: JOKER_API_KEY", + }, + { + desc: "missing username", + envVars: map[string]string{ + "JOKER_API_KEY": "", + "JOKER_USERNAME": "", + "JOKER_PASSWORD": "123", + }, + expected: "joker: some credentials information are missing: JOKER_USERNAME or some credentials information are missing: JOKER_API_KEY", }, } @@ -55,14 +83,22 @@ func TestNewDNSProvider(t *testing.T) { func TestNewDNSProviderConfig(t *testing.T) { testCases := []struct { desc string - authKey string + apiKey string + username string + password string baseURL string expected string expectedBaseURL string }{ { - desc: "success", - authKey: "123", + desc: "success api key", + apiKey: "123", + expectedBaseURL: defaultBaseURL, + }, + { + desc: "success username and password", + username: "123", + password: "123", expectedBaseURL: defaultBaseURL, }, { @@ -70,15 +106,27 @@ func TestNewDNSProviderConfig(t *testing.T) { expected: "joker: credentials missing", expectedBaseURL: defaultBaseURL, }, + { + desc: "missing credentials: username", + expected: "joker: credentials missing", + username: "123", + expectedBaseURL: defaultBaseURL, + }, + { + desc: "missing credentials: password", + expected: "joker: credentials missing", + password: "123", + expectedBaseURL: defaultBaseURL, + }, { desc: "Base URL should ends with /", - authKey: "123", + apiKey: "123", baseURL: "http://example.com", expectedBaseURL: "http://example.com/", }, { desc: "Base URL already ends with /", - authKey: "123", + apiKey: "123", baseURL: "http://example.com/", expectedBaseURL: "http://example.com/", }, @@ -87,7 +135,10 @@ func TestNewDNSProviderConfig(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { config := NewDefaultConfig() - config.APIKey = test.authKey + config.APIKey = test.apiKey + config.Username = test.username + config.Password = test.password + if test.baseURL != "" { config.BaseURL = test.baseURL }