database

package
v0.3.1 Latest Latest
Warning

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

Go to latest
Published: Aug 11, 2020 License: Apache-2.0 Imports: 26 Imported by: 0

Documentation

Overview

Package database manages database connections and ORM integration.

Index

Constants

View Source
const (

	// MinCodeLength defines the minimum number of digits in a code.
	MinCodeLength = 6
)

Variables

View Source
var (
	ErrVerificationCodeNotFound = errors.New("verification code not found")
	ErrVerificationCodeExpired  = errors.New("verification code expired")
	ErrVerificationCodeUsed     = errors.New("verification code used")
	ErrTokenExpired             = errors.New("verification token expired")
	ErrTokenUsed                = errors.New("verification token used")
	ErrTokenMetadataMismatch    = errors.New("verification token test metadata mismatch")
)
View Source
var (
	// ValidTestTypes is a map containing the valid test types.
	ValidTestTypes = map[string]struct{}{
		"confirmed": {},
		"likely":    {},
		"negative":  {},
	}

	ErrInvalidTestType    = errors.New("invalid test type, must be confirmed, likely, or negative")
	ErrCodeAlreadyExpired = errors.New("code already expired")
	ErrCodeTooShort       = errors.New("verification code must be at least 6 digits")
	ErrTestTooOld         = errors.New("test date is more than 14 day ago")
)
View Source
var CleanupName = "cleanup"
View Source
var (
	ErrCleanupWrongGeneration = errors.New("cleanup wrong generation")
)

Functions

func IsNotFound added in v0.3.0

func IsNotFound(err error) bool

IsNotFound determines if an error is a record not found.

func NewTestDatabaseWithConfig

func NewTestDatabaseWithConfig(tb testing.TB) (*Database, *Config)

NewTestDatabaseWithConfig creates a new database suitable for use in testing. This should not be used outside of testing, but it is exposed in the main package so it can be shared with other packages.

All database tests can be skipped by running `go test -short` or by setting the `SKIP_DATABASE_TESTS` environment variable.

Types

type APIUserType

type APIUserType int
const (
	APIUserTypeDevice APIUserType = 0
	APIUserTypeAdmin  APIUserType = 1
)

type AuthorizedApp

type AuthorizedApp struct {
	gorm.Model
	// AuthorizedApps belong to exactly one realm.
	RealmID uint   `gorm:"unique_index:realm_apikey_name"`
	Realm   *Realm // for loading the owning realm.
	Name    string `gorm:"type:varchar(100);unique_index:realm_apikey_name"`

	// APIKeyPreview is the first few characters of the API key for display
	// purposes. This can help admins in the UI when determining which API key is
	// in use.
	APIKeyPreview string `gorm:"type:varchar(32)"`

	// APIKey is the HMACed API key.
	APIKey string `gorm:"type:varchar(512);unique_index"`

	// APIKeyType s the API key type.
	APIKeyType APIUserType `gorm:"default:0"`
}

AuthorizedApp represents an application that is authorized to verify verification codes and perform token exchanges. This is controlled via a generated API key.

Admin Keys are able to issue diagnosis keys and are not able to perticipate the verification protocol.

func (*AuthorizedApp) GetRealm

func (a *AuthorizedApp) GetRealm(db *Database) (*Realm, error)

GetRealm does a lazy load read of the realm associated with this authorized app.

func (*AuthorizedApp) IsAdminType

func (a *AuthorizedApp) IsAdminType() bool

func (*AuthorizedApp) IsDeviceType

func (a *AuthorizedApp) IsDeviceType() bool

func (AuthorizedApp) TableName

func (AuthorizedApp) TableName() string

TableName definition for the authorized apps relation.

func (*AuthorizedApp) UsageSummary added in v0.3.0

func (a *AuthorizedApp) UsageSummary(db *Database) (*AuthorizedAppStatsSummary, error)

UsageSummary returns the usage summary for the authorized app (api key) over a 1d, 7d, and 30d period. This is expensive so callers should consider caching the value.

type AuthorizedAppStats added in v0.3.0

type AuthorizedAppStats struct {
	gorm.Model
	Date            time.Time `gorm:"unique_index:idx_date_app_realm"`
	AuthorizedAppID uint      `gorm:"unique_index:idx_date_app_realm"`
	RealmID         uint      `gorm:"unique_index:idx_date_app_realm"`
	CodesIssued     uint64
}

AuthorizedAppStats represents statistics related to a user in the database.

func (AuthorizedAppStats) TableName added in v0.3.0

func (AuthorizedAppStats) TableName() string

TableName sets the AuthorizedAppStats table name

type AuthorizedAppStatsSummary added in v0.3.0

type AuthorizedAppStatsSummary struct {
	AuthorizedApp  *AuthorizedApp
	Realm          *Realm
	CodesIssued1d  uint64
	CodesIssued7d  uint64
	CodesIssued30d uint64
}

type CleanupStatus

type CleanupStatus struct {
	gorm.Model
	Type       string `gorm:"type:varchar(50);unique_index"`
	Generation uint
	NotBefore  time.Time
}

func (CleanupStatus) TableName

func (CleanupStatus) TableName() string

TableName sets the CleanupStatus table name

type Config

type Config struct {
	Name              string `env:"DB_NAME" json:",omitempty"`
	User              string `env:"DB_USER" json:",omitempty"`
	Host              string `env:"DB_HOST, default=localhost" json:",omitempty"`
	Port              string `env:"DB_PORT, default=5432" json:",omitempty"`
	SSLMode           string `env:"DB_SSLMODE, default=require" json:",omitempty"`
	ConnectionTimeout uint   `env:"DB_CONNECT_TIMEOUT" json:",omitempty"`
	Password          string `env:"DB_PASSWORD" json:"-"` // ignored by zap's JSON formatter
	SSLCertPath       string `env:"DB_SSLCERT" json:",omitempty"`
	SSLKeyPath        string `env:"DB_SSLKEY" json:",omitempty"`
	SSLRootCertPath   string `env:"DB_SSLROOTCERT" json:",omitempty"`

	// Debug is a boolean that indicates whether the database should log SQL
	// commands.
	Debug bool `env:"DB_DEBUG,default=false"`

	// CacheTTL is the amount of time to cache values. This is enabled on a
	// per-query basis. Not all query results are cached.
	CacheTTL time.Duration `env:"DB_CACHE_TTL, default=5m" json:",omitempty"`

	// Keys is the key management configuration. This is used to resolve values
	// that are encrypted via a KMS.
	Keys keys.Config

	// EncryptionKey is the reference to an encryption/decryption key to use when
	// for application-layer encryption before values are persisted to the
	// database.
	EncryptionKey string `env:"DB_ENCRYPTION_KEY,required"`

	// APIKeyDatabaseHMAC is the HMAC key to use for API keys before storing them
	// in the database.
	APIKeyDatabaseHMAC envconfig.Base64Bytes `env:"DB_APIKEY_DATABASE_KEY,required"`

	// APIKeySignatureHMAC is the HMAC key to sign API keys before returning them
	// to the requestor.
	APIKeySignatureHMAC envconfig.Base64Bytes `env:"DB_APIKEY_SIGNATURE_KEY,required"`

	// Secrets is the secret configuration. This is used to resolve values that
	// are actually pointers to secrets before returning them to the caller. The
	// table implementation is the source of truth for which values are secrets
	// and which are plaintext.
	Secrets secrets.Config
}

Config represents the env var based configuration for database connections.

func (*Config) ConnectionString

func (c *Config) ConnectionString() string

ConnectionString returns the postgresql connection string based on this config.

While this package could be adapted to different databases easily, this file and method in particular would need to change.

func (*Config) Open

func (c *Config) Open(ctx context.Context) (*Database, error)

Open created a DB connection through gorm.

type Database

type Database struct {
	// contains filtered or unexported fields
}

Database is a handle to the database layer for the Exposure Notifications Verification Server.

func NewTestDatabase

func NewTestDatabase(tb testing.TB) *Database

func (*Database) ClaimCleanup

func (db *Database) ClaimCleanup(current *CleanupStatus, lockTime time.Duration) (*CleanupStatus, error)

ClaimCleanup attempts to obtain a lock for the specified `lockTime` so that that type of cleanup can be perofmed exclusively by the owner.

func (*Database) ClaimToken

func (db *Database) ClaimToken(realmID uint, tokenID string, subject *Subject) error

ClaimToken looks up the token by ID, verifies that it is not expired and that the specified subject matches the parameters that were configured when issued.

func (*Database) Close

func (db *Database) Close() error

Close will close the database connection. Should be deferred right after Open.

func (*Database) CreateAuthorizedApp

func (db *Database) CreateAuthorizedApp(realmID uint, name string, apiUserType APIUserType) (string, *AuthorizedApp, error)

CreateAuthorizedApp generates a new API key and assigns it to the specified app. Note that the API key is NOT stored in the database, only a hash. The only time the API key is available is as the string return parameter from invoking this function.

func (*Database) CreateCleanup

func (db *Database) CreateCleanup(cType string) (*CleanupStatus, error)

CreateCleanup is used to create a new 'cleanup' type/row in the database.

func (*Database) CreateRealm

func (db *Database) CreateRealm(name string) (*Realm, error)

func (*Database) CreateUser

func (db *Database) CreateUser(email string, name string, admin bool) (*User, error)

CreateUser creates a user record.

func (*Database) DeleteSMSConfig

func (db *Database) DeleteSMSConfig(s *SMSConfig) error

DeleteSMSConfig removes an SMS configuration record.

func (*Database) DeleteUser

func (db *Database) DeleteUser(u *User) error

DeleteUser removes a user record.

func (*Database) DeleteVerificationCode

func (db *Database) DeleteVerificationCode(code string) error

DeleteVerificationCode deletes the code if it exists. This is a hard delete.

func (*Database) FindAuthorizedAppByAPIKey

func (db *Database) FindAuthorizedAppByAPIKey(apiKey string) (*AuthorizedApp, error)

FindAuthorizedAppByAPIKey located an authorized app based on API key. If no app exists for the given API key, it returns nil.

func (*Database) FindCleanupStatus

func (db *Database) FindCleanupStatus(cType string) (*CleanupStatus, error)

FindCleanupStatus looks up the current cleanup state in the database by cleanup type.

func (*Database) FindTokenByID

func (db *Database) FindTokenByID(tokenID string) (*Token, error)

func (*Database) FindUser

func (db *Database) FindUser(email string) (*User, error)

FindUser reads back a User struct by email address.

func (*Database) FindVerificationCode

func (db *Database) FindVerificationCode(code string) (*VerificationCode, error)

FindVerificationCode find a verification code by the code number.

func (*Database) GenerateAPIKey added in v0.3.0

func (db *Database) GenerateAPIKey(realmID uint) (string, error)

GenerateAPIKey generates a new API key that is bound to the given realm. This API key is NOT stored in the database. API keys are of the format:

key:realmID:hex(hmac)

func (*Database) GenerateAPIKeySignature added in v0.3.0

func (db *Database) GenerateAPIKeySignature(key string) ([]byte, error)

GenerateAPIKeySignature signs the given API key using an HMAC shared secret.

func (*Database) GetAuthorizedAppStats added in v0.3.0

func (db *Database) GetAuthorizedAppStats(a *AuthorizedApp, r *Realm, t time.Time) (*AuthorizedAppStats, error)

func (*Database) GetAuthorizedAppStatsSummary added in v0.3.0

func (db *Database) GetAuthorizedAppStatsSummary(a *AuthorizedApp, r *Realm) (*AuthorizedAppStatsSummary, error)

func (*Database) GetRealm

func (db *Database) GetRealm(realmID uint) (*Realm, error)

func (*Database) GetRealmByName

func (db *Database) GetRealmByName(name string) (*Realm, error)

func (*Database) GetRealms added in v0.3.0

func (db *Database) GetRealms() ([]*Realm, error)

func (*Database) GetUser

func (db *Database) GetUser(id uint) (*User, error)

GetUser reads back a User struct by email address.

func (*Database) GetUserStats added in v0.3.0

func (db *Database) GetUserStats(u *User, r *Realm, t time.Time) (*UserStats, error)

func (*Database) GetUserStatsSummary added in v0.3.0

func (db *Database) GetUserStatsSummary(u *User, r *Realm) (*UserStatsSummary, error)

func (*Database) ListAuthorizedApps

func (db *Database) ListAuthorizedApps(includeDeleted bool) ([]*AuthorizedApp, error)

ListAuthorizedApps retrieves all of the configured authorized apps. Done without pagination, as the expected number of authorized apps is low signal digits.

func (*Database) ListUsers

func (db *Database) ListUsers(includeDeleted bool) ([]*User, error)

ListUsers retrieves all of the configured users. Done without pagination. This is not scoped to realms.

func (*Database) MigrateTo

func (db *Database) MigrateTo(ctx context.Context, target string, rollback bool) error

MigrateTo migrates the database to a specific target migration ID.

func (*Database) Ping added in v0.3.0

func (db *Database) Ping(ctx context.Context) error

Ping attempts a connection and closes it to the database.

func (*Database) PurgeTokens

func (db *Database) PurgeTokens(maxAge time.Duration) (int64, error)

PurgeTokens will delete tokens that have expired since at least the provided maxAge ago. This is a hard delete, not a soft delete.

func (*Database) PurgeVerificationCodes

func (db *Database) PurgeVerificationCodes(maxAge time.Duration) (int64, error)

PurgeVerificationCodes will delete verifications that have expired since at least the provided maxAge ago. This is a hard delete, not a soft delete.

func (*Database) RunMigrations

func (db *Database) RunMigrations(ctx context.Context) error

RunMigrations will apply sequential, transactional migrations to the database

func (*Database) SaveAuthorizedApp added in v0.3.0

func (db *Database) SaveAuthorizedApp(r *AuthorizedApp) error

SaveAuthorizedApp saves the authorized app.

func (*Database) SaveRealm

func (db *Database) SaveRealm(r *Realm) error

func (*Database) SaveSMSConfig

func (db *Database) SaveSMSConfig(s *SMSConfig) error

SaveSMSConfig creates or updates an SMS configuration record.

func (*Database) SaveUser

func (db *Database) SaveUser(u *User) error

SaveUser creates or updates a user record.

func (*Database) SaveVerificationCode

func (db *Database) SaveVerificationCode(vc *VerificationCode, maxAge time.Duration) error

SaveVerificationCode created or updates a verification code in the database. Max age represents the maximum age of the test date [optional] in the record.

func (*Database) UpdateAuthorizedAppStats added in v0.3.0

func (db *Database) UpdateAuthorizedAppStats() error

func (*Database) UpdateUserStats added in v0.3.0

func (db *Database) UpdateUserStats() error

func (*Database) VerifyAPIKeySignature added in v0.3.0

func (db *Database) VerifyAPIKeySignature(key string) (string, uint, error)

VerifyAPIKeySignature verifies the signature matches the expected value for the key. It does this by computing the expected signature and then doing a constant-time comparison against the provided signature.

func (*Database) VerifyCodeAndIssueToken

func (db *Database) VerifyCodeAndIssueToken(realmID uint, verCode string, expireAfter time.Duration) (*Token, error)

VerifyCodeAndIssueToken takes a previously issed verification code and exchanges it for a long term token. The verification code must not have expired and must not have been previously used. Both acctions are done in a single database transaction.

The long term token can be used later to sign keys when they are submitted.

type Realm

type Realm struct {
	gorm.Model

	Name string `gorm:"type:varchar(200);unique_index"`

	AuthorizedApps []*AuthorizedApp

	RealmUsers  []*User `gorm:"many2many:user_realms"`
	RealmAdmins []*User `gorm:"many2many:admin_realms"`

	// Relations to items that blong to a realm.
	Codes  []*VerificationCode
	Tokens []*Token
	// contains filtered or unexported fields
}

Realm represents a tenant in the system. Typically this corresponds to a geography or a public health authority scope. This is used to manage user logins.

func (*Realm) AddAdminUser

func (r *Realm) AddAdminUser(u *User)

AddAdminUser adds to the in memory structure, but does not save. Use SaveRealm to persist.

func (*Realm) AddAuthorizedApp

func (r *Realm) AddAuthorizedApp(a *AuthorizedApp)

AddAuthorizedApp adds to the in memory structure, but does not save. Use SaveRealm to persist.

func (*Realm) AddUser

func (r *Realm) AddUser(u *User)

AddUser add to the in memory structure, but does not save. Use SaveRealm to persist.

func (*Realm) DeleteUserFromRealm

func (r *Realm) DeleteUserFromRealm(db *Database, u *User) error

func (*Realm) DisableAuthorizedApp added in v0.3.0

func (r *Realm) DisableAuthorizedApp(id uint) error

DisableAuthorizedApp disables the given authorized app by id.

func (*Realm) EnableAuthorizedApp added in v0.3.0

func (r *Realm) EnableAuthorizedApp(id uint) error

EnableAuthorizedApp enables the given authorized app by id.

func (*Realm) FindAuthorizedApp added in v0.3.0

func (r *Realm) FindAuthorizedApp(authAppID uint) (*AuthorizedApp, error)

FindAuthorizedApp finds the authorized app by the given id associated to the realm.

func (*Realm) FindAuthorizedAppString added in v0.3.0

func (r *Realm) FindAuthorizedAppString(s string) (*AuthorizedApp, error)

FindAuthorizedAppString finds the authorized app by id where the id is a string.

func (*Realm) GetAuthorizedApps

func (r *Realm) GetAuthorizedApps(db *Database, includeDeleted bool) ([]*AuthorizedApp, error)

GetAuthorizedApps does a lazy load on a realm's authorized apps if they are not already loaded.

func (*Realm) HasSMSConfig added in v0.3.0

func (r *Realm) HasSMSConfig() bool

HasSMSConfig returns true if the realm has SMS configuration, false otherwise.

func (*Realm) LoadRealmUsers

func (r *Realm) LoadRealmUsers(db *Database, includeDeleted bool) error

LoadRealmUsers performs a lazy load over the users of the realm. Really only needed for user admin scenarios.

func (*Realm) SMSConfig

func (r *Realm) SMSConfig() (*SMSConfig, error)

SMSConfig returns the SMS config for the realm, if one exists.

func (*Realm) SMSProvider added in v0.3.0

func (r *Realm) SMSProvider() (sms.Provider, error)

SMSProvider returns the SMS provider for the realm. If no sms configuration exists, it returns nil. If any errors occur creating the provider, they are returned.

type SMSConfig

type SMSConfig struct {
	gorm.Model

	// SMS Config belongs to exactly one realm.
	RealmID uint `gorm:"unique_index"`
	Realm   Realm

	// ProviderType is the SMS provider type - it's used to determine the
	// underlying configuration.
	ProviderType sms.ProviderType `gorm:"type:varchar(100)"`

	// Twilio configuration options.
	TwilioAccountSid string `gorm:"type:varchar(250)"`
	TwilioAuthToken  string `gorm:"type:varchar(250)"`
	TwilioFromNumber string `gorm:"type:varchar(16)"`
	// contains filtered or unexported fields
}

SMSConfig represents and SMS configuration.

func (*SMSConfig) AfterFind added in v0.3.0

func (r *SMSConfig) AfterFind() error

AfterFind runs after a record is found. It's used to mutate values (such as auth tokens) before storing them on the struct. Do not call this function, gorm calls it automatically.

func (*SMSConfig) AfterSave added in v0.3.0

func (r *SMSConfig) AfterSave() error

AfterSave runs after records are saved. It's used to mutate values. Do not call this function, gorm calls it automatically.

func (*SMSConfig) BeforeSave added in v0.3.0

func (r *SMSConfig) BeforeSave() error

BeforeSave runs before records are saved/created. It's used to mutate values (such as the auth tokens) before storing them in the database. Do not call this function, gorm calls it automatically.

func (*SMSConfig) SMSProvider added in v0.3.0

func (r *SMSConfig) SMSProvider() (sms.Provider, error)

SMSProvider gets the SMS provider for the given realm. The values are cached.

type Subject

type Subject struct {
	TestType    string
	SymptomDate *time.Time
}

Subject represents the data that is used in the 'sub' field of the token JWT.

func ParseSubject

func ParseSubject(sub string) (*Subject, error)

func (*Subject) String

func (s *Subject) String() string

func (*Subject) SymptomInterval

func (s *Subject) SymptomInterval() uint32

type Token

type Token struct {
	gorm.Model
	// Tokens belong to one realm.
	RealmID     uint
	TokenID     string `gorm:"type:varchar(200); unique_index"`
	TestType    string `gorm:"type:varchar(20)"`
	SymptomDate *time.Time
	Used        bool `gorm:"default:false"`
	ExpiresAt   time.Time
}

Token represents an issued "long term" from a validated verification code.

func (*Token) FormatSymptomDate

func (t *Token) FormatSymptomDate() string

FormatSymptomDate returns YYYY-MM-DD formatted test date, or "" if nil.

func (*Token) Subject

func (t *Token) Subject() *Subject

type User

type User struct {
	gorm.Model
	Email           string `gorm:"type:varchar(250);unique_index"`
	Name            string `gorm:"type:varchar(100)"`
	Admin           bool   `gorm:"default:false"`
	LastRevokeCheck time.Time
	Realms          []*Realm `gorm:"many2many:user_realms"`
	AdminRealms     []*Realm `gorm:"many2many:admin_realms"`
}

User represents a user of the system

func (*User) CanAdminRealm

func (u *User) CanAdminRealm(realmID uint) bool

func (*User) CanViewRealm

func (u *User) CanViewRealm(realmID uint) bool

func (*User) GetRealm

func (u *User) GetRealm(realmID uint) *Realm

func (*User) MultipleRealms

func (u *User) MultipleRealms() bool

type UserStats added in v0.3.0

type UserStats struct {
	gorm.Model
	Date        time.Time `gorm:"unique_index:idx_date_user_realm"`
	UserID      uint      `gorm:"unique_index:idx_date_user_realm"`
	RealmID     uint      `gorm:"unique_index:idx_date_user_realm"`
	CodesIssued uint64
}

UserStats represents statistics related to a user in the database.

func (UserStats) TableName added in v0.3.0

func (UserStats) TableName() string

TableName sets the UserStats table name

type UserStatsSummary added in v0.3.0

type UserStatsSummary struct {
	User           *User
	Realm          *Realm
	CodesIssued1d  uint64
	CodesIssued7d  uint64
	CodesIssued30d uint64
}

type VerificationCode

type VerificationCode struct {
	gorm.Model
	RealmID       uint   // VerificationCodes belong to exactly one realm when issued.
	Code          string `gorm:"type:varchar(20);unique_index"`
	UUID          string `gorm:"type:uuid;unique_index;default:null"`
	Claimed       bool   `gorm:"default:false"`
	TestType      string `gorm:"type:varchar(20)"`
	SymptomDate   *time.Time
	ExpiresAt     time.Time
	IssuingUserID int
	IssuingUser   *User
	IssuingAppID  int
	IssuingApp    *AuthorizedApp
}

VerificationCode represents a verification code in the database.

func (*VerificationCode) FormatSymptomDate

func (v *VerificationCode) FormatSymptomDate() string

FormatSymptomDate returns YYYY-MM-DD formatted test date, or "" if nil.

func (*VerificationCode) IsExpired

func (v *VerificationCode) IsExpired() bool

IsExpired returns true if a verification code has expired.

func (VerificationCode) TableName

func (VerificationCode) TableName() string

TableName sets the VerificationCode table name

func (*VerificationCode) Validate

func (v *VerificationCode) Validate(maxAge time.Duration) error

Validate validates a verification code before save.

Jump to

Keyboard shortcuts

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