Files
libkiwi/auth.go
T
2026-06-06 17:32:09 -04:00

140 lines
3.0 KiB
Go

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
}