registerOAuth2Client performs Dynamic Client Registration per RFC 7591 by POSTing client metadata to the registration endpoint and returning the assigned client_id and optional client_secret.
( ctx context.Context, httpClient *http.Client, registrationEndpoint, callbackURL, clientName string, )
| 1531 | // endpoint and returning the assigned client_id and optional |
| 1532 | // client_secret. |
| 1533 | func registerOAuth2Client( |
| 1534 | ctx context.Context, httpClient *http.Client, |
| 1535 | registrationEndpoint, callbackURL, clientName string, |
| 1536 | ) (clientID string, clientSecret string, err error) { |
| 1537 | payload := map[string]any{ |
| 1538 | "client_name": clientName, |
| 1539 | "redirect_uris": []string{callbackURL}, |
| 1540 | "token_endpoint_auth_method": "none", |
| 1541 | "grant_types": []string{"authorization_code", "refresh_token"}, |
| 1542 | "response_types": []string{"code"}, |
| 1543 | } |
| 1544 | |
| 1545 | body, err := json.Marshal(payload) |
| 1546 | if err != nil { |
| 1547 | return "", "", xerrors.Errorf( |
| 1548 | "marshal registration request: %w", err, |
| 1549 | ) |
| 1550 | } |
| 1551 | |
| 1552 | req, err := http.NewRequestWithContext( |
| 1553 | ctx, http.MethodPost, |
| 1554 | registrationEndpoint, bytes.NewReader(body), |
| 1555 | ) |
| 1556 | if err != nil { |
| 1557 | return "", "", xerrors.Errorf( |
| 1558 | "create registration request: %w", err, |
| 1559 | ) |
| 1560 | } |
| 1561 | req.Header.Set("Content-Type", "application/json") |
| 1562 | req.Header.Set("Accept", "application/json") |
| 1563 | |
| 1564 | resp, err := httpClient.Do(req) |
| 1565 | if err != nil { |
| 1566 | return "", "", xerrors.Errorf( |
| 1567 | "POST %s: %w", registrationEndpoint, err, |
| 1568 | ) |
| 1569 | } |
| 1570 | defer resp.Body.Close() |
| 1571 | |
| 1572 | respBody, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) |
| 1573 | if err != nil { |
| 1574 | return "", "", xerrors.Errorf( |
| 1575 | "read registration response: %w", err, |
| 1576 | ) |
| 1577 | } |
| 1578 | |
| 1579 | if resp.StatusCode != http.StatusOK && |
| 1580 | resp.StatusCode != http.StatusCreated { |
| 1581 | // Truncate to avoid leaking verbose upstream errors |
| 1582 | // through the API. |
| 1583 | const maxErrBody = 512 |
| 1584 | errMsg := string(respBody) |
| 1585 | if len(errMsg) > maxErrBody { |
| 1586 | errMsg = errMsg[:maxErrBody] + "..." |
| 1587 | } |
| 1588 | return "", "", xerrors.Errorf( |
| 1589 | "registration endpoint returned HTTP %d: %s", |
| 1590 | resp.StatusCode, errMsg, |