Documentation ¶
Index ¶
- Constants
- Variables
- func CountOrganizations(ctx context.Context) (count int64, err error)
- func CountUsers(ctx context.Context) (count int64, err error)
- func DeleteAPIKey(ctx context.Context, id, orgID ulid.ULID) (err error)
- func DeleteUser(ctx context.Context, userID, orgID ulid.ULID) (err error)
- func GetAPIKeyPermissions(ctx context.Context) (permissions []string, err error)
- type APIKey
- func GetAPIKey(ctx context.Context, clientID string) (key *APIKey, err error)
- func ListAPIKeys(ctx context.Context, orgID, projectID ulid.ULID, prevPage *pagination.Cursor) (keys []*APIKey, cursor *pagination.Cursor, err error)
- func RetrieveAPIKey(ctx context.Context, id ulid.ULID) (key *APIKey, err error)
- func (k *APIKey) AddPermissions(permissions ...string) error
- func (k *APIKey) Create(ctx context.Context) (err error)
- func (k *APIKey) GetLastUsed() (time.Time, error)
- func (k *APIKey) Permissions(ctx context.Context, refresh bool) (_ []string, err error)
- func (k *APIKey) SetLastUsed(ts time.Time)
- func (k *APIKey) SetPermissions(permissions ...string) error
- func (k *APIKey) Status() APIKeyStatus
- func (k *APIKey) ToAPI(ctx context.Context) *api.APIKey
- func (k *APIKey) Update(ctx context.Context) (err error)
- func (k *APIKey) UpdateLastUsed(ctx context.Context) (err error)
- func (k *APIKey) Validate() error
- type APIKeyPermission
- type APIKeyStatus
- type Base
- type ConstraintError
- type Organization
- type OrganizationProject
- type OrganizationUser
- type Permission
- type Role
- type RolePermission
- type User
- func GetUser(ctx context.Context, userID, orgID any) (u *User, err error)
- func GetUserByToken(ctx context.Context, token string) (u *User, err error)
- func GetUserEmail(ctx context.Context, email string, orgID any) (u *User, err error)
- func ListUsers(ctx context.Context, orgID any, prevPage *pagination.Cursor) (users []*User, cursor *pagination.Cursor, err error)
- func (u *User) Create(ctx context.Context, org *Organization, role string) (err error)
- func (u *User) CreateVerificationToken() (err error)
- func (u *User) GetLastLogin() (time.Time, error)
- func (u *User) GetVerificationExpires() (time.Time, error)
- func (u *User) GetVerificationToken() string
- func (u *User) OrgID() (ulid.ULID, error)
- func (u *User) Permissions(ctx context.Context, refresh bool) (_ []string, err error)
- func (u *User) Role() (role string, _ error)
- func (u *User) Save(ctx context.Context) (err error)
- func (u *User) SetAgreement(agreeToS, agreePrivacy bool)
- func (u *User) SetLastLogin(ts time.Time)
- func (u *User) SwitchOrganization(ctx context.Context, orgID any) (err error)
- func (u *User) ToAPI(ctx context.Context) *api.User
- func (u *User) Update(ctx context.Context, orgID any) (err error)
- func (u *User) UpdateLastLogin(ctx context.Context) (err error)
- func (u *User) UserRole(ctx context.Context, orgID ulid.ULID, refresh bool) (role string, err error)
- func (u *User) Validate() error
- type ValidationError
Constants ¶
const (
APIKeyPermissionsSQL = "SELECT name FROM permissions WHERE allow_api_keys=true ORDER BY name"
)
const APIKeyStalenessThreshold = 90 * 24 * time.Hour
Variables ¶
var ( ErrNotFound = errors.New("object not found in the database") ErrInvalidOrganization = errors.New("organization model is not correctly populated") ErrUserOrganization = errors.New("user is not associated with the organization") ErrInvalidUser = errors.New("user model is not correctly populated") ErrInvalidPassword = errors.New("user password should be stored as an argon2 derived key") ErrMissingModelID = errors.New("model does not have an ID assigned") ErrMissingKeyMaterial = errors.New("apikey model requires client id and secret") ErrInvalidSecret = errors.New("apikey secrets should be stored as argon2 derived keys") ErrMissingOrgID = errors.New("model does not have an organization ID assigned") ErrMissingProjectID = errors.New("model requires project id") ErrInvalidProjectID = errors.New("invalid project id for apikey") ErrMissingKeyName = errors.New("apikey model requires name") ErrMissingCreatedBy = errors.New("apikey model requires created by") ErrNoPermissions = errors.New("apikey model requires permissions") ErrInvalidPermission = errors.New("invalid permission specified for apikey") ErrModifyPermissions = errors.New("cannot modify permissions on an existing APIKey object") ErrMissingPageSize = errors.New("cannot list database without a page size") ErrInvalidCursor = errors.New("could not compute the next page of results") ErrDuplicate = errors.New("unique constraint violated on model") ErrMissingRelation = errors.New("foreign key relation violated on model") ErrNotNull = errors.New("not null constraint violated on model") ErrConstraint = errors.New("database constraint violated") )
Functions ¶
func CountOrganizations ¶ added in v0.3.0
CountOrganizations returns the number of organizations currently in the database.
func CountUsers ¶
CountUsers returns the number of users currently in the database.
func DeleteAPIKey ¶
DeleteAPIKey by ID restricted to the organization ID supplied. E.g. in order to delete an API Key both the key ID and the organization ID must match otherwise an ErrNotFound is returned. This method expects to only delete one row at a time and rolls back the operation if multiple rows are deleted.
For auditing purposes the api key is deleted from the live api_keys table but then inserted without a secret into the revoked_api_keys table. This is because logs and other information like events may have API key IDs associated with them; in order to trace those keys back to its owners, some information must be preserved. The revoked table helps maintain those connections without a lot of constraints.
func DeleteUser ¶ added in v0.4.0
Delete a user by passing in the user ID and the organization ID The user ID and the organization ID must be present in the organization_users table TODO: implement the function
Types ¶
type APIKey ¶
type APIKey struct { Base ID ulid.ULID KeyID string Secret string Name string OrgID ulid.ULID ProjectID ulid.ULID CreatedBy ulid.ULID Source sql.NullString UserAgent sql.NullString LastUsed sql.NullString Partial bool // contains filtered or unexported fields }
APIKey is a model that represents a row in the api_keys table and provides database functionality for interacting with api key data. It should not be used for API serialization.
func GetAPIKey ¶
GetAPIKey by Client ID. This query is executed as a read-only transaction. When fetching by Client ID we expect that an authentication is being performed, so the secret is also fetched.
func ListAPIKeys ¶
func ListAPIKeys(ctx context.Context, orgID, projectID ulid.ULID, prevPage *pagination.Cursor) (keys []*APIKey, cursor *pagination.Cursor, err error)
ListAPIKeys returns a paginated collection of APIKeys from the database filtered by the orgID and optionally by the projectID. To fetch all keys for an organization, pass a zero-valued ULID as the projectID. The number of results returned is controlled by the prevPage cursor. To return the first page with a default number of results pass nil for the prevPage; otherwise pass an empty page with the specified PageSize. If the prevPage contains an EndIndex then the next page is returned.
An apikeys slice with the maximum length of the page size will be returned or an empty (nil) slice if there are no results. If there is a next page of results, e.g. there is another row after the page returned, then a cursor will be returned to compute the next page token with.
func RetrieveAPIKey ¶
RetrieveAPIKey by ID. This query is executed as a read-only transaction. When retrieving a key by ID we expect that this is for informational purposes and not for authentication so the secret is not returned.
func (*APIKey) AddPermissions ¶
AddPermissions to an APIKey that has not been created yet. If the APIKey has an ID an error is returned since APIKey permissions cannot be modified. This method will append permissions to the uncreated Key.
func (*APIKey) Create ¶
Create an APIKey, inserting the record in the database. If the record already exists or a uniqueness constraint is violated an error is returned. Creating the APIKey will also associate the permissions with the key. If a permission does not exist in the database, an error will be returned. This method sets the ID, partial, created, and modified timestamps even if the user has already set them on the model. If the APIKey does not have a client ID and secret, they're generated before the model is created.
NOTE: the OrgID and ProjectID on the APIKey must be associated in the Quarterdeck database otherwise an ErrInvalidProjectID error is returned. Callers should populate the OrgID from the claims of the user and NOT from user submitted input.
func (*APIKey) GetLastUsed ¶
GetLastUsed returns the parsed LastUsed timestamp if it is not null. If it is null then a zero-valued timestamp is returned without an error.
func (*APIKey) Permissions ¶
Returns the Permissions associated with the user as a list of strings. The permissions are cached to prevent multiple queries; use the refresh bool to force a new database query to reload the permissions of the user.
func (*APIKey) SetLastUsed ¶
SetLastUsed ensures the LastUsed timestamp is serialized to a string correctly.
func (*APIKey) SetPermissions ¶
SetPermissions on an APIKey that has not been created yet. If the APIKey has an ID an error is returned since APIKey permissions cannot be modified. This method will overwrite any permissions already added to the APIKey.
func (*APIKey) Status ¶ added in v0.5.0
func (k *APIKey) Status() APIKeyStatus
Status of the APIKey based on the LastUsed timestamp if the api keys have not been revoked. If the keys have never been used the unused status is returned; if they have not been used in 90 days then the stale status is returned; otherwise the apikey is considered active unless it has been revoked.
func (*APIKey) ToAPI ¶
ToAPI creates a Quarterdeck API response from the model, populating all fields except for the ClientSecret since this is not returned in most API requests.
func (*APIKey) Update ¶
Update an APIKey, modifying the record in the database with the key's ID and OrgID. After the key is updated, it is populated with the latest results from the database and returned to the user.
NOTE: only the name field is updated so only limited validation is performed.
func (*APIKey) UpdateLastUsed ¶
UpdateLastUsed is a quick helper to set the last_used and modified timestamp.
type APIKeyPermission ¶
APIKeyPermission is a model representing a many-to-many mapping between api keys and permissions. This model is primarily used by the APIKey and Permission models and is not intended for direct use generally.
type APIKeyStatus ¶ added in v0.5.0
type APIKeyStatus string
const ( APIKeyStatusUnknown APIKeyStatus = "" APIKeyStatusUnused APIKeyStatus = "unused" APIKeyStatusActive APIKeyStatus = "active" APIKeyStatusStale APIKeyStatus = "stale" APIKeyStatusRevoked APIKeyStatus = "revoked" )
type Base ¶
The Base model provides model audit functionality for setting created and modified timestamps in the database so we can track how rows are being modified over time.
func (*Base) GetCreated ¶
Return the parsed created timestamp.
func (*Base) GetModified ¶
Return the parsed modified timestamp.
func (*Base) SetCreated ¶
Sets the created timestamp as the string formatted representation of the ts.
func (*Base) SetModified ¶
Sets the modified timestamp as the string formatted representation of the ts.
type ConstraintError ¶ added in v0.3.0
type ConstraintError struct {
// contains filtered or unexported fields
}
ConstraintError attempts to parse a sqlite3.ErrConstraint error into a model error.
func (*ConstraintError) Error ¶ added in v0.3.0
func (e *ConstraintError) Error() string
func (*ConstraintError) Is ¶ added in v0.3.0
func (e *ConstraintError) Is(target error) bool
func (*ConstraintError) Unwrap ¶ added in v0.3.0
func (e *ConstraintError) Unwrap() error
type Organization ¶
type Organization struct { Base ID ulid.ULID Name string Domain string // contains filtered or unexported fields }
Organization is a model that represents a row in the organizations table and provides database functionality for interacting with an organizations's data. It should not be used for API serialization.
func GetOrg ¶ added in v0.3.0
func GetOrg(ctx context.Context, id any) (org *Organization, err error)
func (*Organization) Create ¶ added in v0.3.0
func (o *Organization) Create(ctx context.Context) (err error)
Create an organization, inserting the record into the database. If the record already exists or a uniqueness constraint is violated an error is returned. This method sets the ID, created, and modified timestamps even if the user has already set them.
If the organization name or domain are empty a validation error is returned.
func (*Organization) ProjectCount ¶ added in v0.5.0
func (o *Organization) ProjectCount() int
func (*Organization) ToAPI ¶ added in v0.4.0
func (o *Organization) ToAPI() *api.Organization
type OrganizationProject ¶ added in v0.3.0
type OrganizationProject struct { Base OrgID ulid.ULID ProjectID ulid.ULID }
OrganizationProject is a model representing the many-to-one mapping between projects and organizations. The project model is not stored in the database (but rather in the tenant database) so only the projectID is stored, but it must be unique to prevent a security hole where a user issues APIKeys to a project in an organization that they do not belong to. Before issuing APIKeys with a projectID, Quarterdeck checks to ensure that the project actually belongs to the organization via a lookup in this table. Otherwise, all information about the project is stored in Tenant.
func (*OrganizationProject) Exists ¶ added in v0.3.0
func (op *OrganizationProject) Exists(ctx context.Context) (ok bool, err error)
Exists checks if an organization project mapping exists in order to verify that a project is allowed to be associated with an APIKey or other claims resource for the user with the specified OrgID claims. Only the OrgID and ProjectID are used for this so no preliminary fetch from the database is required to execute the query.
func (*OrganizationProject) Save ¶ added in v0.3.0
func (op *OrganizationProject) Save(ctx context.Context) (err error)
Save an organization project mapping to the database by creating a record. Organization project mappings can only be created and deleted, not updated, so if the mapping already exists an error is returned.
NOTE: because this is a security condition, the OrgID in the OrganizationProject model must come from the user claims and not from user input!
type OrganizationUser ¶
type OrganizationUser struct { Base OrgID ulid.ULID UserID ulid.ULID RoleID int64 // contains filtered or unexported fields }
OrganizationUser is a model representing a many-to-many mapping between users and organizations and describes the role each user has in their organization. This model is primarily used by the User and Organization models and is not intended for direct use generally.
NOTE: a user can only have one role in an organization, so roles must be defined as overlapping sets rather than as disjoint sets where users have multiple roles.
func GetOrgUser ¶ added in v0.3.0
func GetOrgUser(ctx context.Context, userID, orgID any) (ou *OrganizationUser, err error)
func (*OrganizationUser) Organization ¶ added in v0.3.0
func (o *OrganizationUser) Organization(ctx context.Context, refresh bool) (_ *Organization, err error)
Returns the organization associated with the OrganizationUser struct. The object is cached on the struct and can be refreshed on demand. TODO: fetch on GetOrgUser to reduce number of raft queries.
func (*OrganizationUser) Role ¶ added in v0.3.0
Returns the role associated with the organization and user. The object is cached on the struct and can be refreshed on demand. TODO: fetch on GetOrgUser to reduce number of raft queries.
type Permission ¶
type Permission struct { Base ID int64 Name string Description sql.NullString AllowAPIKeys bool AllowRoles bool }
Permission is a model that represents a row in the permissions table and provides database functionality for interacting with permission data. It should not be used for API serialization.
func GetPermission ¶ added in v0.5.0
func GetPermission(ctx context.Context, name string) (p *Permission, err error)
type Role ¶
type Role struct { Base ID int64 Name string Description sql.NullString // contains filtered or unexported fields }
Role is a model that represents a row in the roles table and provides database functionality for interacting with role data. It should not be used for API serialization.
func (*Role) Permissions ¶ added in v0.3.0
type RolePermission ¶
RolePermission is a model representing a many-to-many mapping between roles and permissions. This model is primarily used by the Role and Permission models and is not intended for direct use generally.
type User ¶
type User struct { Base ID ulid.ULID Name string Email string Password string AgreeToS sql.NullBool AgreePrivacy sql.NullBool EmailVerified bool EmailVerificationExpires sql.NullString EmailVerificationToken sql.NullString EmailVerificationSecret []byte LastLogin sql.NullString // contains filtered or unexported fields }
User is a model that represents a row in the users table and provides database functionality for interacting with a user's data. It should not be used for API serialization. Users may be retrieved from the database either via their ID (e.g. from the sub claim in a JWT token) or via their email address (e.g. on login). The user password should be stored as an argon2 hash and should be verified using the argon2 hashing algorithm. Care should be taken to ensure this model stays secure.
Users are associated with one or more organizations. When the user model is loaded from the database one organization must be supplied so that permissions and role can be retrieved correctly. If no orgID is supplied then one of the user's organizations is selected from the database as the default organiztion. Use the SwitchOrganization method to switch the user model to a different organization to retrieve a different and permissions or use the UserRoles method to determine which organizations the user belongs to.
func GetUser ¶
GetUser by ID. The ID can be either a string, which is parsed into a ULID or it can be a valid ULID. The query is then executed as a read-only transaction against the database and the user is returned. An orgID can be specified to load the user in that organization. If the orgID is Null then one of the organizations the user belongs to is loaded (the default user organization).
func GetUserByToken ¶ added in v0.5.0
GetUser by verification token by executing a read-only transaction against the database.
func GetUserEmail ¶
GetUser by Email. This query is executed as a read-only transaction. An orgID can be specified to load the user in that organization. If the orgID is Null then one of the organizations the user belongs to is loaded (the default user organization).
func ListUsers ¶ added in v0.3.0
func ListUsers(ctx context.Context, orgID any, prevPage *pagination.Cursor) (users []*User, cursor *pagination.Cursor, err error)
ListUsers returns a paginated collection of users filtered by the orgID. The orgID must be a valid non-zero value of type ulid.ULID, a string representation of a type ulid.ULID, or a slice of bytes The number of users resturned is controlled by the prevPage cursor. To return the first page with a default number of results pass nil for the prevPage; Otherwise pass an empty page with the specified PageSize. If the prevPage contains an EndIndex then the next page is returned.
A users slice with the maximum length of the page size will be returned or an empty (nil) slice if there are no results. If there is a next page of results, e.g. there is another row after the page returned, then a cursor will be returned to compute the next page token with.
func (*User) Create ¶
Create a user, inserting the record in the database. If the record already exists or a uniqueness constraint is violated an error is returned. The user will also be associated with the specified organization and the specified role name. If the organization doesn't exist, it will be created. If the role does not exist in the database, an error will be returned. This method sets the user ID, created and modified timestamps even if they are already set on the model.
func (*User) CreateVerificationToken ¶ added in v0.5.0
CreateVerificationToken creates a new verification token for the user, setting the email verification fields on the model and returning the token that should be given to the user.
func (*User) GetLastLogin ¶
GetLastLogin returns the parsed LastLogin timestamp if it is not null. If it is null then a zero-valued timestamp is returned without an error.
func (*User) GetVerificationExpires ¶ added in v0.5.0
GetVerificationExpires returns the verification token expiration time for the user or a zero time if the token is null.
func (*User) GetVerificationToken ¶ added in v0.5.0
GetVerificationToken returns the verification token for the user if it is not null.
func (*User) OrgID ¶ added in v0.3.0
OrgID returns the organization id that the user was loaded for. If the model doesn't have an orgID then an error is returned. This method requires that the user was loaded using one of the fetch and catch methods such as GetUserID or that the SwitchOrganization method was used to load the user.
func (*User) Permissions ¶
Returns the Permissions associated with the user as a list of strings. The permissions are cached to prevent multiple queries; use the refresh bool to force a new database query to reload the permissions of the user.
func (*User) Role ¶ added in v0.3.0
Role returns the current role for the user in the organization the user was loaded for. If the model does not have an orgID or the user doesn't belong to the organization then an error is returned. This method requires that the UserRoles have been fetched and cached (e.g. that the user was retrieved from the database with an organization or that SwitchOrganization) was used.
func (*User) Save ¶
Save a user's name, email, password, agreements, verification data, and last login. The modified timestamp is set to the current time and neither the ID nor the created timestamp are modified. This query is executed as a write-transaction. The user must be fully populated and exist in the database for this method to execute successfully.
func (*User) SetAgreement ¶ added in v0.3.0
SetAgreement marks if the user has accepted the terms of service and privacy policy.
func (*User) SetLastLogin ¶
SetLastLogin ensures the LastLogin timestamp is serialized to a string correctly.
func (*User) SwitchOrganization ¶ added in v0.3.0
SwitchOrganization loads the user role and permissions for the specified organization returning an error if the user is not in the specified organization.
func (*User) Update ¶ added in v0.3.0
Update a User in the database. The requester needs to be in the same orgID as the user. This check is performed by verifying that the orgID and the user_id exist in the organization_users table The orgID must be a valid non-zero value of type ulid.ULID, a string representation of a type ulid.ULID, or a slice of bytes
func (*User) UpdateLastLogin ¶
UpdateLastLogin is a quick helper method to set the last_login and modified timestamp.
func (*User) UserRole ¶
func (u *User) UserRole(ctx context.Context, orgID ulid.ULID, refresh bool) (role string, err error)
Returns the name of the user role associated with the user for the specified organization. Queries the cached information when the user is fetched unless refresh is true, which reloads the cached information from the database on demand.
type ValidationError ¶
type ValidationError struct {
// contains filtered or unexported fields
}
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string
func (*ValidationError) Is ¶
func (e *ValidationError) Is(target error) bool
func (*ValidationError) Unwrap ¶
func (e *ValidationError) Unwrap() error