package client import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "time" ) // LoginParams represents the parameters for login. type LoginParams struct { User string `json:"user"` Password string `json:"password,omitempty"` Token string `json:"token,omitempty"` TOTPCode string `json:"totp_code,omitempty"` } // LoginRequest represents a login request to the MCIAS API. type LoginRequest struct { Version string `json:"version"` Login LoginParams `json:"login"` } // TOTPVerifyRequest represents a TOTP verification request. type TOTPVerifyRequest struct { Version string `json:"version"` Username string `json:"username"` TOTPCode string `json:"totp_code"` } // TokenResponse represents the response from a login or TOTP verification request. type TokenResponse struct { Token string `json:"token"` Expires int64 `json:"expires"` TOTPEnabled bool `json:"totp_enabled,omitempty"` } // ErrorResponse represents an error response from the API. type ErrorResponse struct { Error string `json:"error"` ErrorCode string `json:"error_code,omitempty"` } // LoginWithPassword authenticates with the MCIAS server using a username and password. // If TOTP is enabled for the user, the TOTPEnabled field in the response will be true, // and the client will need to call VerifyTOTP to complete authentication. func (c *Client) LoginWithPassword(ctx context.Context, username, password string) (*TokenResponse, error) { loginReq := LoginRequest{ Version: "v1", Login: LoginParams{ User: username, Password: password, }, } jsonData, err := json.Marshal(loginReq) if err != nil { return nil, fmt.Errorf("error creating request: %w", err) } url := fmt.Sprintf("%s/v1/login/password", c.BaseURL) req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData)) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Content-Type", "application/json") resp, err := c.HTTPClient.Do(req) if err != nil { return nil, fmt.Errorf("failed to send request: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } if resp.StatusCode != http.StatusOK { var errResp ErrorResponse if unmarshalErr := json.Unmarshal(body, &errResp); unmarshalErr == nil { return nil, fmt.Errorf("API error: %s (code: %s)", errResp.Error, errResp.ErrorCode) } return nil, fmt.Errorf("API error: %s", resp.Status) } var tokenResp TokenResponse if err := json.Unmarshal(body, &tokenResp); err != nil { return nil, fmt.Errorf("failed to parse response: %w", err) } // Update client with authentication info c.Token = tokenResp.Token c.Username = username return &tokenResp, nil } // LoginWithToken authenticates with the MCIAS server using a token. func (c *Client) LoginWithToken(ctx context.Context, username, token string) (*TokenResponse, error) { loginReq := LoginRequest{ Version: "v1", Login: LoginParams{ User: username, Token: token, }, } jsonData, err := json.Marshal(loginReq) if err != nil { return nil, fmt.Errorf("error creating request: %w", err) } url := fmt.Sprintf("%s/v1/login/token", c.BaseURL) req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData)) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Content-Type", "application/json") resp, err := c.HTTPClient.Do(req) if err != nil { return nil, fmt.Errorf("failed to send request: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } if resp.StatusCode != http.StatusOK { var errResp ErrorResponse if unmarshalErr := json.Unmarshal(body, &errResp); unmarshalErr == nil { return nil, fmt.Errorf("API error: %s (code: %s)", errResp.Error, errResp.ErrorCode) } return nil, fmt.Errorf("API error: %s", resp.Status) } var tokenResp TokenResponse if err := json.Unmarshal(body, &tokenResp); err != nil { return nil, fmt.Errorf("failed to parse response: %w", err) } // Update client with authentication info c.Token = tokenResp.Token c.Username = username return &tokenResp, nil } // VerifyTOTP verifies a TOTP code for a user. func (c *Client) VerifyTOTP(ctx context.Context, username, totpCode string) (*TokenResponse, error) { totpReq := TOTPVerifyRequest{ Version: "v1", Username: username, TOTPCode: totpCode, } jsonData, err := json.Marshal(totpReq) if err != nil { return nil, fmt.Errorf("error creating request: %w", err) } url := fmt.Sprintf("%s/v1/login/totp", c.BaseURL) req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData)) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Content-Type", "application/json") resp, err := c.HTTPClient.Do(req) if err != nil { return nil, fmt.Errorf("failed to send request: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } if resp.StatusCode != http.StatusOK { var errResp ErrorResponse if unmarshalErr := json.Unmarshal(body, &errResp); unmarshalErr == nil { return nil, fmt.Errorf("API error: %s (code: %s)", errResp.Error, errResp.ErrorCode) } return nil, fmt.Errorf("API error: %s", resp.Status) } var tokenResp TokenResponse if err := json.Unmarshal(body, &tokenResp); err != nil { return nil, fmt.Errorf("failed to parse response: %w", err) } // Update client with authentication info c.Token = tokenResp.Token c.Username = username return &tokenResp, nil } // IsTokenExpired checks if the token is expired. func (c *Client) IsTokenExpired(expiryTime int64) bool { return expiryTime > 0 && expiryTime < time.Now().Unix() }