Skip to content

Commit

Permalink
Add OAuth2 package
Browse files Browse the repository at this point in the history
Create a new oauth2 package for functionality to work with OAuth2. This
package will be used to authenticate CLI clients with the BSR.
  • Loading branch information
emcfarlane committed Jul 4, 2024
1 parent 70d8712 commit 9eb5a94
Show file tree
Hide file tree
Showing 3 changed files with 250 additions and 0 deletions.
170 changes: 170 additions & 0 deletions private/pkg/oauth2/device.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// Copyright 2020-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package oauth2

import (
"net/url"
)

const (
// DeviceRegistrationPath is the path for the device registration endpoint.
DeviceRegistrationPath = "/oauth2/device/registration"
// DeviceAuthorizationPath is the path for the device authorization endpoint.
DeviceAuthorizationPath = "/oauth2/device/authorization"
// DeviceTokenPath is the path for the device token endpoint.
TokenPath = "/oauth2/device/token"

Check failure on line 27 in private/pkg/oauth2/device.go

View workflow job for this annotation

GitHub Actions / lint

G101: Potential hardcoded credentials (gosec)
)

const (
// DeviceAuthorizationGrantType is the grant type for the device authorization flow.
DeviceAuthorizationGrantType = "urn:ietf:params:oauth:grant-type:device_code"
)

// The following error codes are defined by RFC 8628 Section 3.5 Device Authorization Response.
const (
// ErrorCodeAuthorizationPending is a pending device authorization grant as the
// end user hasn't yet completed the user interaction steps.
ErrorCodeAuthorizationPending ErrorCode = "authorization_pending"
// ErrorCodeSlowDown is returned for a pending device authorization grant and
// polling should continue, but the interval MUST be increased by 5 seconds for
// all subsequent requests.
ErrorCodeSlowDown ErrorCode = "slow_down"
// ErrorCodeAccessDenied is returned when the device authorization request was denied.
ErrorCodeAccessDenied ErrorCode = "access_denied"
// ErrorCodeExpiredToken is the device_code has expired, and the device authorization
// session has concluded. The client MAY commence a new device authorization request but
// SHOULD wait for user interaction before restarting to avoid unnecessary polling.
ErrorCodeExpiredToken ErrorCode = "expired_token"
)

// DeviceRegistrationRequest describes an OpenID Connect Dynamic Client Registration 1.0 request
// for dynamic client registration. It is a subset of the full specification.
// It does not require a redirect URI or grant types for the device authorization flow.
// https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationRequest
type DeviceRegistrationRequest struct {
// Name of the client to be presented to the end user.
ClientName string `json:"client_name"`
}

// Devic describes a successful OpenID Connect Dynamic Client Registration 1.0 response
// for dynamic client registration.
type DeviceRegistrationResponse struct {
// ClientID is the unique client identifier.
ClientID string `json:"client_id"`
// ClientSecret is the client secret. May be empty.
ClientSecret string `json:"client_secret,omitempty"`
// ClientIDIssuedAt is the time at which the ClientID was issued in seconds since the Unix epoch.
ClientIDIssuedAt int `json:"client_id_issued_at"`
// ClientSecretExpiresAt is the time at which the client_secret will expire in seconds since the Unix epoch.
ClientSecretExpiresAt int `json:"client_secret_expires_at,omitempty"`
}

// DeviceAuthorizationRequest describes an RFC 8628 Device Authorization Request.
// https://datatracker.ietf.org/doc/html/rfc8628#section-3.1
type DeviceAuthorizationRequest struct {
// ClientID is the unique client identifier.
ClientID string `json:"client_id"`
// ClientSecret is the client secret. May be empty.
ClientSecret string `json:"client_secret,omitempty"`
}

// ToValues converts the DeviceAuthorizationRequest to url.Values.
func (d *DeviceAuthorizationRequest) ToValues() url.Values {
values := make(url.Values, 2)
values.Set("client_id", d.ClientID)
if d.ClientSecret != "" {
values.Set("client_secret", d.ClientSecret)
}
return values
}

// FromValues converts the url.Values to a DeviceAuthorizationRequest.
func (d *DeviceAuthorizationRequest) FromValues(values url.Values) error {
d.ClientID = values.Get("client_id")
d.ClientSecret = values.Get("client_secret")
return nil
}

// DeviceAuthorizationResponse describes a successful RFC 8628 Device Authorization Response
// https://datatracker.ietf.org/doc/html/rfc8628#section-3.2
type DeviceAuthorizationResponse struct {
// DeviceCode is the device verification code.
DeviceCode string `json:"device_code"`
// UserCode is the end-user verification code.
UserCode string `json:"user_code"`
// VerificationURI is the verification URI that the end user should visit to
// enter the user_code.
VerificationURI string `json:"verification_uri"`
// VerificationURIComplete is the verification URI that includes the user_code.
VerificationURIComplete string `json:"verification_uri_complete,omitempty"`
// ExpiresIn is the lifetime in seconds of the "device_code" and "user_code".
ExpiresIn int `json:"expires_in"`
// Interval is the minimum amount of time in seconds that the client SHOULD wait
// between polling requests to the token endpoint.
Interval int `json:"interval,omitempty"`
}

// DeviceAccessTokenRequest describes an RFC 8628 Device Token Request.
// https://datatracker.ietf.org/doc/html/rfc8628#section-3.4
type DeviceAccessTokenRequest struct {
// ClientID is the client identifier issued to the client during the registration process.
ClientID string `json:"client_id"`
// ClientSecret is the client secret. May be empty.
ClientSecret string `json:"client_secret,omitempty"`
// DeviceCode is the device verification code.
DeviceCode string `json:"device_code"`
// GrantType is the grant type for the device authorization flow. Must be
// set to "urn:ietf:params:oauth:grant-type:device_code".
GrantType string `json:"grant_type"`
}

// ToValues converts the DeviceTokenRequest to url.Values.
func (d *DeviceAccessTokenRequest) ToValues() url.Values {
values := make(url.Values, 4)
values.Set("client_id", d.ClientID)
if d.ClientSecret != "" {
values.Set("client_secret", d.ClientSecret)
}
values.Set("device_code", d.DeviceCode)
values.Set("grant_type", d.GrantType)
return values
}

// FromValues converts the url.Values to a DeviceTokenRequest.
func (d *DeviceAccessTokenRequest) FromValues(values url.Values) error {
d.ClientID = values.Get("client_id")
d.ClientSecret = values.Get("client_secret")
d.DeviceCode = values.Get("device_code")
d.GrantType = values.Get("grant_type")
return nil
}

// DeviceAccessTokenResponse describes a successful RFC 8628 Device Token Response.
// https://datatracker.ietf.org/doc/html/rfc8628#section-3.5
type DeviceAccessTokenResponse struct {
// AccessToken is the access token that can be used to access the protected resources.
AccessToken string `json:"access_token"`
// TokenType is the type of the token issued as described in RFC 6749 Section 7.1.
// https://datatracker.ietf.org/doc/html/rfc6749#section-7.1
TokenType string `json:"token_type"`
// ExpiresIn is the lifetime in seconds of the access token.
ExpiresIn int `json:"expires_in,omitempty"`
// RefreshToken may be used to obtain new access tokens using the same authoization
// grant. May be empty.
RefreshToken string `json:"refresh_token,omitempty"`
// Scope is the scope of the access token as described in RFC 6749 Section 3.3.
// https://datatracker.ietf.org/doc/html/rfc6749#section-3.3
Scope string `json:"scope,omitempty"`
}
61 changes: 61 additions & 0 deletions private/pkg/oauth2/oauth2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright 2020-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package oauth2 contains functionality to work with OAuth2.
package oauth2

import (
"fmt"
)

// ErrorCode is an OAuth2 error code.
type ErrorCode string

// The folowing error codes are defined by RFC 6749 Section 5.2 Error Response.

Check failure on line 25 in private/pkg/oauth2/oauth2.go

View workflow job for this annotation

GitHub Actions / lint

`folowing` is a misspelling of `following` (misspell)
const (
// ErrorCodeInvalidRequest is an invalid or malformed request error.
ErrorCodeInvalidRequest ErrorCode = "invalid_request"
// ErrorCodeInvalidClient is a client authentication error.
ErrorCodeInvalidClient ErrorCode = "invalid_client"
// ErrorCodeInvalidGrant is an invalid grant error.
ErrorCodeInvalidGrant ErrorCode = "invalid_grant"
// ErrorCodeUnauthorizedClient is an unauthorized client error.
ErrorCodeUnauthorizedClient ErrorCode = "unauthorized_client"
// ErrorCodeUnsupportedGrantType is an unsupported grant type error.
ErrorCodeUnsupportedGrantType ErrorCode = "unsupported_grant_type"
// ErrorCodeInvalidScope is an invalid scope error.
ErrorCodeInvalidScope ErrorCode = "invalid_scope"
)

// Error is an OAuth2 error.
type Error struct {
// ErrorCode is the error code.
ErrorCode ErrorCode `json:"error"`
// ErrorDescription is a human-readable description of the error. May be empty.
ErrorDescription string `json:"error_description,omitempty"`
// ErrorURI is a URI for the error. May be empty.
ErrorURI string `json:"error_uri,omitempty"`
}

// Error implements error.
func (e *Error) Error() string {
s := fmt.Sprintf("oauth2: %q", e.ErrorCode)
if e.ErrorDescription != "" {
s += fmt.Sprintf(" %q", e.ErrorDescription)
}
if e.ErrorURI != "" {
s += fmt.Sprintf(" %q", e.ErrorURI)
}
return s
}
19 changes: 19 additions & 0 deletions private/pkg/oauth2/usage.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 9eb5a94

Please sign in to comment.