accounts

package module
v0.4.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: 4 Imported by: 2

README

accounts

accounts is a subsystem of Lockbox. Its responsibility is to keep track of different ways a user can log in to your service.

When logging in, a user may be identified by an email address, Google account, public key, or other identifier. The accounts module are how these identifiers get associated with a "user" in your system.

Design Goals

accounts 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, accounts 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.

accounts tries to enable a user-friendly authentication experience. This means allowing users to register multiple ways to authenticate with a service and use them interchangeably, rather than asking users to remember which method they used when registering.

Implementation

accounts is implemented largely as a datastore and access mechanism for an Account type. Account types just tie an Account ID to to a profile ID. Profile IDs are expected to be controlled by accounts--it will decide what new users' profile IDs are, and will not offload that responsibility to another system.

The IDs for Accounts must be unique, even across different authentication methods. They are expected to be the same value a user would specify when choosing a login method--an email address, a username, etc. The intended user experience is that a user would enter their Account ID in an input box for email-based and username-based authentication methods, or click a separate button for an OAuth or OpenID login experience.

For email and username logins, the email or username as entered should be used as the Account ID--accounts will make sure to use case-insensitive uniqueness constraints and comparisons.

For OAuth and OpenID authentication methods, use whatever the authentication provider uses as an account ID, unless an email address is available, in which case, use that as the Account ID, instead. This allows users to log in using an email-based flow or an OAuth or OpenID flow interchangeably.

Scope

accounts is solely responsible for managing the connection between a user and their various ways of logging into the system.

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

  • Which user is authenticating?
  • How can a specific user authenticate?
  • How does a user add a new way to authenticate?

The things accounts is explicitly not expected to do include:

  • Actually authenticate users. accounts is meant to tie an authentication method to a user, not actually do the authentication.
  • Managing ACLs.
  • Managing user profile information. Applications have specific enough needs for profiles that accounts can't reasonably abstract them. Instead, accounts just stores the profile ID for a user, which applications can then use to retrieve the profile information.

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 accounts provides a mapping of login methods to logical users.

The accounts package provides the definitions of the service and its boundaries. It sets up the Account type, which represents a mapping of a login method to a user within your application, and the Storer interface, which defines how to implement data storage backends for these Accounts.

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 (
	// ErrAccountNotFound is returned when an Account was expected but could not be found.
	ErrAccountNotFound = errors.New("account not found")
	// ErrAccountAlreadyExists is returned when attempting to create an Account that already exists.
	ErrAccountAlreadyExists = errors.New("account already exists")
	// ErrProfileIDAlreadyExists is returned when an account is registered by the ProfileID already exists.
	ErrProfileIDAlreadyExists = errors.New("profileID already exists")
)

Functions

func ByLastUsedDesc

func ByLastUsedDesc(accounts []Account)

ByLastUsedDesc sorts the passed slice of Accounts by their LastUsed property, with the most recent times at the lower indices.

Types

type Account

type Account struct {
	// ID is a globally-unique identifier for how the user identifies
	// themselves to your application. It is case-insensitive.
	ID string

	// ProfileID is how your application should identify the user. It is an
	// opaque string that will be automatically generated for you.
	ProfileID string

	// Created is the time at which the Account was first registered.
	Created time.Time

	// LastUsed is the time at which the Account was last authenticated
	// with by the user, completing the password challenge or clicking the
	// link in the email, or however the account is authenticated.
	LastUsed time.Time

	// LastSeen is the time at which the Account was last seen acting. This
	// is different from the time it was last authenticated; when an
	// authentication token issued for this Account is used, LastSeen
	// should be updated. When the Account is issued an authentication
	// token, LastUsed and LastSeen should both be updated.
	LastSeen time.Time

	// IsRegistration should be set to true when the Account is the first
	// Account a user is trying to register. This enables extra validation
	// logic to ensure that ProfileIDs are unique for logical users, but
	// that multiple Accounts can be registered to a single logical user.
	IsRegistration bool
}

Account is a representation of a user's identifier. It maps the identifier (email, username, whatever) to a profile ID, allowing users to have multiple identifiers that are all interchangeable.

func Apply

func Apply(change Change, account Account) Account

Apply returns a copy of the specified Account with the changes requested by the specified Change applied.

func FillDefaults

func FillDefaults(account Account) Account

FillDefaults sets a reasonable default for any of the properties of the specified Account that both have reasonable defaults and are set to the zero value when FillDefaults is called. It returns a copy of the specified Account with those defaults applied.

type Change

type Change struct {
	LastUsed *time.Time
	LastSeen *time.Time
}

Change represents a requested change to one or more of an Account's mutable properties.

func (Change) IsEmpty

func (c Change) IsEmpty() bool

IsEmpty returns true if the Change would not result in a change, no matter which Account it was applied to.

type Dependencies

type Dependencies struct {
	Storer Storer
}

Dependencies holds all the information that we want to make available to all our functions, but that are orthogonal enough to not warrant their own place in every function's signature.

type Storer

type Storer interface {
	Create(ctx context.Context, account Account) error
	Get(ctx context.Context, id string) (Account, error)
	Update(ctx context.Context, id string, change Change) error
	Delete(ctx context.Context, id string) error
	ListByProfile(ctx context.Context, profileID string) ([]Account, error)
}

Storer dictates how Accounts will be persisted and how to interact with those persisted Accounts.

Directories

Path Synopsis
Package apiv1 provides a JSON API for interacting with accounts.
Package apiv1 provides a JSON API for interacting with accounts.
storers
memory
Package memory provides an in-memory implementation of the lockbox.dev/accounts.Storer interface.
Package memory provides an in-memory implementation of the lockbox.dev/accounts.Storer interface.
postgres
Package postgres provides an implementation of the lockbox.dev/accounts.Storer interface that stores data in a PostgreSQL database.
Package postgres provides an implementation of the lockbox.dev/accounts.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