package libkiwi import ( "context" "errors" "fmt" "net/http" "net/url" "strings" ) type Credentials struct { Username string Password string Remember bool } func loginForm(xfToken string, creds Credentials) url.Values { rem := []byte{'0'} if creds.Remember { rem[0] = '1' } return url.Values{ "_xfToken": {xfToken}, "login": {creds.Username}, "password": {creds.Password}, "remember": {string(rem)}, } } func (kf *KF) newLoginRequest(ctx context.Context, creds Credentials) (*http.Request, error) { u := kf.urlFromPath("login") resp, err := kf.Get(ctx, u) if err != nil { return nil, err } defer resp.Body.Close() xfToken, err := XFToken(resp.Body) if err != nil { return nil, err } form := loginForm(xfToken, creds) // i.e. https://kiwifarms.net/login/login reqURL := fmt.Sprintf("%s/login", u.String()) req, err := http.NewRequestWithContext(ctx, "POST", reqURL, strings.NewReader(form.Encode())) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") return req, nil } func (kf *KF) Login(ctx context.Context, creds Credentials) (*http.Response, error) { req, err := kf.newLoginRequest(ctx, creds) if err != nil { return nil, err } resp, err := kf.Do(req) if err != nil { return nil, err } if resp.StatusCode != 200 { // TODO } return resp, nil } func twoFactorForm(xfToken string, code uint32, u *url.URL) url.Values { return url.Values{ "code": {fmt.Sprint(code)}, "provider": {"totp"}, "trust": {"1"}, "confirm": {"1"}, "remember": {"1"}, "_xfRedirect": {fmt.Sprintf("https://%s/", u.Hostname())}, "_xfResponseType": {"json"}, "_xfToken": {xfToken}, "_xfWithData": {"1"}, "_xfRequestUri": {u.RequestURI()}, } } func (kf *KF) newTwoFactorRequest(ctx context.Context, resp *http.Response, code uint32) (*http.Request, error) { u := resp.Request.URL if !strings.Contains(u.Path, "two-step") { // TODO: move err to proper type with url included. return nil, errors.New("Did not redirect to two-step page.") } xfToken, err := XFToken(resp.Body) if err != nil { return nil, err } form := twoFactorForm(xfToken, code, u) reqURL := fmt.Sprintf("https://%s/login/two-step", u.Hostname()) req, err := http.NewRequestWithContext(ctx, "POST", reqURL, strings.NewReader(form.Encode())) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") return req, nil } func (kf *KF) TwoFactorAuth(ctx context.Context, resp *http.Response, code uint32) (*http.Response, error) { req, err := kf.newTwoFactorRequest(ctx, resp, code) if err != nil { return nil, err } return kf.Do(req) } func (kf *KF) RefreshSession(ctx context.Context) error { // Clear any existing session token to request a new one. setCookie(kf.domain, kf.client.Jar, &http.Cookie{ Name: "xf_session", Value: "", }) resp, err := kf.Get(ctx, kf.domain) if err != nil { return err } resp.Body.Close() return nil }