clients

package module
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 2, 2022 License: MIT Imports: 8 Imported by: 1

README

clients

clients is a subsystem of Lockbox. Its responsibility is to manage the clients that will be interacting with the system.

Clients are just software executing requests. The clients subsystem provides for clients that have redirect-based authentication schemes and for clients that have authentication schemes requiring a secret. The job of the clients subsystem is to store the data on these clients.

Design Goals

clients is meant to be a discrete subsystem in the overall Lockbox system. It tries to have clear boundaries of responsibility and limit its responsibilities to only the things that it is uniquely situated to do. Like all Lockbox subsystems, clients is meant to be an interchangeable part of the system, easily replaceable. All of its functionality should be exposed through its API, instead of relying on other subsystems importing it directly.

clients assumes at this point that a trusted system is executing requests against its APIs, and its API uses an HMAC-based scheme for authentication that will not scale well to untrusted third parties executing requests against it. If third parties can register clients independently, it is recommended that a separate service handles these registrations, calling through to the clients subsystem on the backend.

Implementation

clients is implemented largely as a datastore and access mechanism for an Client and RedirectURI types. Client types have a unique ID per client, which is how they should be programmatically referenced, and a name, which is how they should be referenced to end users. When Client types are generated with a secret component, that component is stored as a PBKDF2 hash using SHA-256. The data model allows for the hashing schema to be changed without invalidating prior secrets.

The API uses an HMAC authentication scheme, expecting the request to be signed with a secret that only authorized parties have. The server uses the secret to verify the signature. The design allows for keys to be rotated while still being able to accept signatures from older keys as part of the transition. Anyone holding the secret is able to create, update, and delete any Client.

The HMAC authentication system was chosen to limit the complexity of the limitation and allow it to not rely on other subsystems for authentication of requests. This yields a more limited authentication system for clients API requests, but one that has fewer dependencies.

RedirectURI types are URIs that are registered on Client types as a redirect target for authentication purposes. These types have an opaque, randomly generated ID, which is how they should be identified programmatically. These URIs can either be a full URI or a base URI that will serve as a prefix and allow clients to be authenticated by redirecting to any URL the request specifies that begins with that prefix.

Client types may have zero or more RedirectURI types associated with them. Each RedirectURI type may only be associated with a single Client.

Scope

clients is solely responsible for managing the list of clients and their authentication information, along with any restrictions placed on which scopes they may use.

The questions clients is meant to answer for the system include:

  • Is this a valid API client?
  • Does this client use this secret?
  • Which scopes can this client access?
  • Is this a valid URI to redirect to for this client?
  • How should this client be displayed to the user?

Repository Structure

The base directory of the repository is used to set the logical framework and shared types that will be used to talk about the subsystem. This largely means defining types and interfaces.

The storers directory contains a collection of implementations of the Storer interface, each in their own package. These packages should only have unit tests, if any tests. The Storer acceptance tests in storer_test.go have common acceptance testing for Storer implementations, and all Storer implementations in the storers directory should register their tests there. If the tests have setup requirements like databases or credentials, the tests should only register themselves if these credentials are found.

The apiv1 directory contains the first version of the API interface. Breaking changes should be published in a separate apiv2 package, so that both versions of the API can be run simultaneously.

Documentation

Overview

Package clients provides a record of conceptual clients that will be interacting with Lockbox.

Clients represent an actor in the system, a deployment of software that needs access to certain Lockbox (and third-party) APIs. Clients are largely useful for keeping track of where requests came from and limiting the scopes available in certain situations.

The clients package provides the definitions of the service and its boundaries. It sets up the Client type, which represents an API consumer, the RedirectURI type, which represents a URI that a client's authentication requests are able to be redirected to, and the Storer interface, which defines how to implement data storage backends for these Clients and RedirectURIs.

This package can be thought of as providing the types and helpers that form the conceptual framework of the subsystem, but with very little functionality provided by itself. Instead, implementations of the interfaces and sub-packages using these types are where most functionality will actually live.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrClientAlreadyExists is returned when a client with the same ID
	// already exists in a Storer.
	ErrClientAlreadyExists = errors.New("client already exists")
	// ErrClientNotFound is returned when a client can't be located in a
	// Storer.
	ErrClientNotFound = errors.New("client not found")
	// ErrIncorrectSecret is returned when a client tries to authenticate
	// with an invalid secret.
	ErrIncorrectSecret = errors.New("incorrect client secret")
	// ErrUnsupportedSecretScheme is returned when a client uses a secret
	// scheme that we don't know how to use.
	ErrUnsupportedSecretScheme = errors.New("an unsupported secret scheme was used")
)

Functions

func RedirectURIsByURI

func RedirectURIsByURI(uris []RedirectURI)

RedirectURIsByURI returns `uris` sorted by their URI property, with URIs that are lexicographically lower returned first.

Types

type Change

type Change struct {
	Name         *string
	SecretHash   *string
	SecretScheme *string
}

Change represents a change we'd like to make to a Client. Nil values always represent "no change", whereas empty values will be interpreted as a desire to set the property to the empty value.

func ChangeSecret

func ChangeSecret(newSecret []byte) (Change, error)

ChangeSecret generates a Change that will update a Client's secret.

func (Change) IsEmpty

func (c Change) IsEmpty() bool

IsEmpty returns true if none of the fields in Change are set.

type Client

type Client struct {
	ID           string    // unique ID per client
	Name         string    // friendly name for this client
	SecretHash   string    // hash of unique secret to authenticate with (optional)
	SecretScheme string    // the hashing scheme used for the secret
	Confidential bool      // whether this is a confidential (true) or public (false) client
	CreatedAt    time.Time // timestamp of creation
	CreatedBy    string    // the HMAC key that created this client
	CreatedByIP  string    // the IP that created this client
}

Client represents an API client.

func Apply

func Apply(change Change, client Client) Client

Apply returns a Client with Change applied to it.

func (Client) CheckSecret

func (c Client) CheckSecret(attempt string) error

CheckSecret returns nil if the passed secret is correct for the Client, or ErrIncorrectSecret if the secret is incorrect. Any other error signals data corruption.

type ErrRedirectURIAlreadyExists

type ErrRedirectURIAlreadyExists struct {
	ID  string
	URI string // the URI that already exists
	Err error  // the error that was returned, if any
}

ErrRedirectURIAlreadyExists is returned when a redirect URI already exists in a Storer.

func (ErrRedirectURIAlreadyExists) Error

Error fills the error interface for RedirectURIs.

type RedirectURI

type RedirectURI struct {
	ID          string    // unique ID per redirect URI
	URI         string    // the URI to redirect to
	IsBaseURI   bool      // whether this is the full URI (false) or just a base (true)
	ClientID    string    // the ID of the Client this redirect URI applies to
	CreatedAt   time.Time // the timestamp this redirect URI was created at
	CreatedBy   string    // the HMAC key that created this redirect URI
	CreatedByIP string    // the IP that created this redirect URI
}

RedirectURI represents a URI that we'll redirect to as part of the OAuth 2 dance for a Client. The RedirectURI is an important part of authorizing a client, especially a public one, as it prevents others from using a Client's ID.

type Storer

type Storer interface {
	Create(ctx context.Context, client Client) error
	Get(ctx context.Context, id string) (Client, error)
	ListRedirectURIs(ctx context.Context, clientID string) ([]RedirectURI, error)
	Update(ctx context.Context, id string, change Change) error
	Delete(ctx context.Context, id string) error
	AddRedirectURIs(ctx context.Context, uris []RedirectURI) error
	RemoveRedirectURIs(ctx context.Context, ids []string) error
}

Storer is an interface for storing, retrieving, and modifying Clients and the metadata surrounding them.

Directories

Path Synopsis
Package apiv1 provides a JSON API for interacting with clients.
Package apiv1 provides a JSON API for interacting with clients.
storers
memory
Package memory provides an in-memory implementation of the lockbox.dev/clients.Storer interface.
Package memory provides an in-memory implementation of the lockbox.dev/clients.Storer interface.
postgres
Package postgres provides an implementation of the lockbox.dev/clients.Storer interface that stores data in a PostgreSQL database.
Package postgres provides an implementation of the lockbox.dev/clients.Storer interface that stores data in a PostgreSQL database.
postgres/migrations
Package migrations provides access to the SQL migrations used to set up a PostgreSQL database for the postgres Storer implementation.
Package migrations provides access to the SQL migrations used to set up a PostgreSQL database for the postgres Storer implementation.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL