diff options
| author | wikiapiserver | 2026-06-25 12:45:50 +0200 |
|---|---|---|
| committer | wikiapiserver | 2026-06-25 12:45:50 +0200 |
| commit | 742cd195c0018bcbc6e748d9100b643ffe1f6358 (patch) | |
| tree | 91d674f549efdb99a341e15d20b07515386e3d01 | |
| parent | ad6b1f3138cd3cd953f9caa6ab5483f0d1ac03eb (diff) | |
| download | wikiapiserver-742cd195c0018bcbc6e748d9100b643ffe1f6358.tar.gz | |
feat: integrate Wikimedia Enterprise auth API on register
Register now calls POST /v1/login on the Wikimedia auth endpoint
to obtain refresh_token and access_token. Tokens are hashed (SHA-256)
before storage. If the API call fails, registration fails.
| -rw-r--r-- | db/db.go | 64 |
1 files changed, 58 insertions, 6 deletions
@@ -6,8 +6,12 @@ import ( "crypto/sha256" "database/sql" "encoding/hex" + "encoding/json" "errors" "fmt" + "io" + "net/http" + "net/url" "strings" "time" @@ -74,6 +78,45 @@ func sha256hex(s string) string { return hex.EncodeToString(h[:]) } +// WikimediaTokens holds the tokens returned by the Wikimedia auth API. +type WikimediaTokens struct { + RefreshToken string `json:"refresh_token"` + AccessToken string `json:"access_token"` +} + +// Wikimedialogin sends credentials to the Wikimedia Enterprise auth API +// and returns the refresh and access tokens. +func Wikimedialogin(ctx context.Context, username, password string) (*WikimediaTokens, error) { + body := fmt.Sprintf("username=%s&password=%s", + url.PathEscape(username), url.PathEscape(password)) + + req, err := http.NewRequestWithContext(ctx, "POST", + "https://auth.enterprise.wikimedia.com/v1/login", + strings.NewReader(body)) + if err != nil { + return nil, fmt.Errorf("new request: %w", err) + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, fmt.Errorf("wikimedia login request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + bs, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("wikimedia login failed (status %d): %s", resp.StatusCode, string(bs)) + } + + var tokens WikimediaTokens + if err := json.NewDecoder(resp.Body).Decode(&tokens); err != nil { + return nil, fmt.Errorf("decode wikimedia tokens: %w", err) + } + + return &tokens, nil +} + // --- error helpers --- func isDupKeyError(err error) bool { @@ -82,13 +125,22 @@ func isDupKeyError(err error) bool { // --- queries --- -// CreateAccount inserts a new account with username and plaintext password. -// Tokens are not generated here; they are set later via the Wikimedia API. +// CreateAccount registers the user via the Wikimedia auth API, +// then persists the account and tokens to the database. +// If the Wikimedia API call fails, registration fails. func (d *DB) CreateAccount(ctx context.Context, username, plaintextPW string) (*Account, error) { + // Obtain tokens from Wikimedia Enterprise auth API + tokens, err := Wikimedialogin(ctx, username, plaintextPW) + if err != nil { + return nil, fmt.Errorf("wikimedia login: %w", err) + } + res, err := d.conn.ExecContext(ctx, `INSERT INTO account (username, password, refresh_token, access_token, access_token_created) - VALUES (?, ?, '', '', NOW())`, + VALUES (?, ?, ?, ?, NOW())`, username, plaintextPW, + sha256hex(tokens.RefreshToken), + sha256hex(tokens.AccessToken), ) if err != nil { if isDupKeyError(err) { @@ -106,9 +158,9 @@ func (d *DB) CreateAccount(ctx context.Context, username, plaintextPW string) (* return &Account{ ID: id, Username: username, - RefreshToken: "", - AccessToken: "", - AccessTokenExpiry: now, + RefreshToken: tokens.RefreshToken, + AccessToken: tokens.AccessToken, + AccessTokenExpiry: now.Add(accessTokenTTL), CreatedAt: now, }, nil } |
