umbrella

package module
v0.8.1 Latest Latest
Warning

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

Go to latest
Published: Jan 1, 2025 License: BSD-2-Clause Imports: 15 Imported by: 3

README

umbrella

Go Reference Go Report Card

Package umbrella provides a simple authentication mechanism for an HTTP endpoint. With it, you can wrap any endpoint that should have its access restricted. In addition, it provides additional its own handler for registering new user, activating it and, naturally, signing in and out.

⚠️ The project is in beta, under heavy construction, and may introduce breaking changes in releases before v1.0.0.

Table of Contents

  1. Sample code
  2. Database connection
  3. User model
  4. Features + Roadmap
  5. Motivation

Sample code

A working application can be found in the cmd/example1. Type make run-example1 to start an HTTP server and check the endpoints as shown below. jq is used to parse out the token from JSON output, however, it can be done manually as well.

# run the application
% make run-example1
# ...some output...

# sign in to get a token
% UMB_TOKEN=$(curl -s -X POST -d "email=admin@example.com&password=admin" http://localhost:8001/umbrella/login | jq -r '.data.token')

# call restricted endpoint without the token
% curl http://localhost:8001/secret_stuff/ 
YouHaveToBeLoggedIn

# call it again with token
% curl -H "Authorization: Bearer $UMB_TOKEN" http://localhost:8001/secret_stuff/
SecretStuffOnlyForAdmin%

# remove temporary postgresql docker
make clean

Database connection

The module needs to store users and sessions in the database. If not attached otherwise, struct-db-postgres will be used as an ORM by default.

To attach a custom ORM, it needs to implement the ORM interface. In the orm.go file, there is an example on how the previously mentioned DB module is wrapped in a struct that has all the methods required by ORM interface. Pass &UmbrellaConfig instance with ORM field to the NewUmbrella constructor to attach your object.

User model

Umbrella comes with its own User and Session structs. However, there might be a need to use a different user model containing more fields, with a separate ORM. Hence, similarily to previous paragraph, an interface called UserInterface has been defined. A custom user struct must implement that interface's methods.

To do the above:

  1. set NoUserConstructor to true in the &UmbrellaConfig argument when calling NewUmbrella
  2. create new &umbrella.Interfaces object with User field and attach it to Interfaces field of umbrella controller.

Features

  • Wrapper support for any HTTP handler
  • Data storage in PostgreSQL database by default
  • Customisable database driver and ORM
  • Flexible User model
  • Optional endpoints for sign-in (creating session objects with access tokens) and sign-out (deactivating sessions and tokens)
  • Optional endpoints for user registration and activation
  • Hooks triggered after successful actions like registration or sign-in
  • Option to use cookies instead of the authorisation header
  • Support for redirection headers after successful or failed sign-in attempts
  • User struct validation during user registration
  • Customisable tag names for field validation
Roadmap
  • Simple permission system

Motivation

While building a backend REST API for a colleague in retail, I needed a simple way to secure HTTP endpoints with basic authentication. The goal was straightforward: users would log in with an email and password, receive a token with an expiration time, and use it to interact with the backend API. A frontend application handled this flow.

A few months later, I was approached with a similar request, this time for an internal company application that required user registration and activation.

More recently, as I began developing a platform for prototyping where I used the code, I realised that this small yet essential piece of code could be valuable to others. And so, I decided to share it here.

Documentation

Index

Constants

View Source
const DisableCheck = 8
View Source
const DisableConfirm = 2
View Source
const DisableLogin = 4
View Source
const DisableRegister = 1
View Source
const FlagSessionActive = 1
View Source
const FlagSessionLoggedOut = 2
View Source
const FlagTypeAllow = 4
View Source
const FlagUserActive = 1
View Source
const FlagUserAllowLogin = 4
View Source
const FlagUserEmailConfirmed = 2
View Source
const ForTypeEveryone = 1
View Source
const ForTypeUser = 4
View Source
const OpsCreate = 8
View Source
const OpsDelete = 64
View Source
const OpsList = 128
View Source
const OpsRead = 16
View Source
const OpsUpdate = 32
View Source
const RegisterAllowedToLogin = 32
View Source
const RegisterConfirmed = 16
View Source
const VERSION = "0.8.1"

Variables

This section is empty.

Functions

func GetAuthorizationBearerToken

func GetAuthorizationBearerToken(r *http.Request) string

func GetPermissionFlagsMultipleBitChoice

func GetPermissionFlagsMultipleBitChoice() map[int]string

func GetPermissionForTypeSingleChoice

func GetPermissionForTypeSingleChoice() map[int]string

func GetPermissionOpsMultipleBitChoice

func GetPermissionOpsMultipleBitChoice() map[int]string

func GetSessionFlagsSingleChoice

func GetSessionFlagsSingleChoice() map[int]string

func GetUserIDFromRequest

func GetUserIDFromRequest(r *http.Request) int64

Types

type DefaultUser

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

DefaultUserModel is default implementation of UserInterface using struct-db-postgres package

func (*DefaultUser) CreateDBTable

func (g *DefaultUser) CreateDBTable() error

func (*DefaultUser) GetByEmail

func (g *DefaultUser) GetByEmail(email string) (bool, error)

func (*DefaultUser) GetByEmailActivationKey

func (g *DefaultUser) GetByEmailActivationKey(key string) (bool, error)

func (*DefaultUser) GetByID

func (g *DefaultUser) GetByID(id int64) (bool, error)

func (*DefaultUser) GetEmail

func (g *DefaultUser) GetEmail() string

func (*DefaultUser) GetEmailActivationKey

func (g *DefaultUser) GetEmailActivationKey() string

func (*DefaultUser) GetExtraField

func (g *DefaultUser) GetExtraField(n string) string

func (*DefaultUser) GetFlags

func (g *DefaultUser) GetFlags() int64

func (*DefaultUser) GetID

func (g *DefaultUser) GetID() int64

func (*DefaultUser) GetPassword

func (g *DefaultUser) GetPassword() string

func (*DefaultUser) GetUser

func (g *DefaultUser) GetUser() interface{}

func (*DefaultUser) Save

func (g *DefaultUser) Save() error

func (*DefaultUser) SetEmail

func (g *DefaultUser) SetEmail(e string)

func (*DefaultUser) SetEmailActivationKey

func (g *DefaultUser) SetEmailActivationKey(k string)

func (*DefaultUser) SetExtraField

func (g *DefaultUser) SetExtraField(n string, v string)

func (*DefaultUser) SetFlags

func (g *DefaultUser) SetFlags(flags int64)

func (*DefaultUser) SetPassword

func (g *DefaultUser) SetPassword(p string)

type ErrUmbrella

type ErrUmbrella struct {
	Op  string
	Err error
}

ErrUmbrella wraps original error that occurred in Err with name of the operation/step that failed, which is in Op field

func (ErrUmbrella) Error

func (e ErrUmbrella) Error() string

func (ErrUmbrella) Unwrap

func (e ErrUmbrella) Unwrap() error

type HTTPResponse

type HTTPResponse struct {
	OK      int8                   `json:"ok"`
	ErrText string                 `json:"err_text"`
	Data    map[string]interface{} `json:"data"`
}

HTTPResponse is a base structure for all the HTTP responses from HTTP endpoints

func NewHTTPResponse

func NewHTTPResponse(ok int8, errText string) HTTPResponse

NewHTTPResponse returns new HTTPResponse object

type HandlerConfig

type HandlerConfig struct {
	UseCookie          string
	CookiePath         string
	SuccessRedirectURL string
	FailureRedirectURL string
}

type Hooks

type Hooks struct {
	PostRegisterSuccess func(http.ResponseWriter, string) bool
	PostConfirmSuccess  func(http.ResponseWriter) bool
	PostLoginSuccess    func(http.ResponseWriter, string, string, int64) bool
	PostCheckSuccess    func(http.ResponseWriter, string, int64, bool) bool
	PostLogoutSuccess   func(http.ResponseWriter, string) bool
}

type Interfaces

type Interfaces struct {
	User func() UserInterface
}

type JWTConfig

type JWTConfig struct {
	Key               string
	ExpirationMinutes int
	Issuer            string
}

type ORM

type ORM interface {
	// RegisterStruct initializes a specific object. ORMs often need to reflect the object to get the fields, build SQL queries etc.
	// When doing that, certain things such as tags can be inherited from another object. This is in the scenario where there is a root object (eg. Product) that contains all the validation tags and
	// another struct with less fields should be used as an input for API (eg. Product_WithoutCertainFields). In such case, there is no need to re-define tags such as validation.
	// Parameter `forceNameForDB` allows forcing another struct name (which later is used for generating table name).
	// This interface is based on the struct2db module and that module allows some cascade operations (such as delete or update). For this to work, and when certain fields are other structs, ORM must go
	// deeper and initializes that guys as well. When setting useOnlyRootFromInheritedObj to true, it's being avoided.
	RegisterStruct(obj interface{}, inheritFromObj interface{}, overwriteExisting bool, forceNameForDB string, useOnlyRootFromInheritedObj bool) error
	// CreateTables create database tables for struct instances
	CreateTables(objs ...interface{}) error
	// DeleteMultiple removes struct items by their ids
	DeleteMultiple(obj interface{}, filters map[string]interface{}) error
	// Get fetches data from the database and returns struct instances. Hence, it requires a constructor for the returned objects. Apart from the self-explanatory fields, filters in a format of (field name, any value)
	// can be added, and each returned object (based on a database row) can be transformed into anything else.
	Get(newObjFunc func() interface{}, order []string, limit int, offset int, filters map[string]interface{}, rowObjTransformFunc func(interface{}) interface{}) ([]interface{}, error)
	// GetCount returns number of struct items found in the database
	GetCount(newObjFunc func() interface{}, filters map[string]interface{}) (int64, error)
	// Load populates struct instance's field values with database values
	Load(obj interface{}, id string) error
	// Save stores (creates or updates) struct instance in the appropriate database table
	Save(obj interface{}) error
	// Delete removes struct instance from the database table
	Delete(obj interface{}) error
}

type ORMError

type ORMError interface {
	// IsInvalidFilters returns true when error is caused by invalid value of the filters when getting objects
	IsInvalidFilters() bool
	// Unwraps unwarps the original error
	Unwrap() error
	// Error returns error string
	Error() string
}

type Permission

type Permission struct {
	ID             int64  `json:"id"`
	Flags          int64  `json:"flags"`
	ForType        int8   `json:"for_type"`
	ForItem        int64  `json:"for_item"`
	Ops            int64  `json:"ops"`
	ToType         string `json:"to_type"`
	ToItem         int64  `json:"to_item"`
	CreatedAt      int64  `json:"created_at"`
	CreatedBy      int64  `json:"created_by"`
	LastModifiedAt int64  `json:"last_modified_at"`
	LastModifiedBy int64  `json:"last_modified_by"`
}

type Session

type Session struct {
	ID          int64  `json:"session_id"`
	Flags       int64  `json:"flags"`
	Key         string `json:"key" 2db:"uniq lenmin:32 lenmax:2000"`
	ExpiresAt   int64  `json:"expires_at"`
	UserID      int64  `json:"user_id" 2db:"req"`
	Description string `json:"string"`
}

type Umbrella

type Umbrella struct {
	Hooks           *Hooks
	Interfaces      *Interfaces
	Flags           int
	UserExtraFields []UserExtraField
	// contains filtered or unexported fields
}

func NewUmbrella

func NewUmbrella(dbConn *sql.DB, tblPrefix string, jwtConfig *JWTConfig, cfg *UmbrellaConfig) *Umbrella

func (Umbrella) ConfirmEmail

func (u Umbrella) ConfirmEmail(key string) error

func (Umbrella) CreateDBTables

func (u Umbrella) CreateDBTables() error

func (Umbrella) CreateUser

func (u Umbrella) CreateUser(email string, pass string, extraFields map[string]string) (string, error)

func (Umbrella) GeneratePassword

func (u Umbrella) GeneratePassword(pass string) (string, error)

func (Umbrella) GetHTTPHandler

func (u Umbrella) GetHTTPHandler(uri string) http.Handler

func (Umbrella) GetHTTPHandlerWrapper

func (u Umbrella) GetHTTPHandlerWrapper(next http.Handler, config HandlerConfig) http.Handler

func (Umbrella) GetLoginHTTPHandler

func (u Umbrella) GetLoginHTTPHandler(config HandlerConfig) http.Handler

func (Umbrella) GetLogoutHTTPHandler

func (u Umbrella) GetLogoutHTTPHandler(config HandlerConfig) http.Handler

func (Umbrella) GetUserOperationAllowedTypes

func (u Umbrella) GetUserOperationAllowedTypes(i int64, o int) (map[string]bool, error)

type UmbrellaConfig

type UmbrellaConfig struct {
	TagName           string
	NoUserConstructor bool
	ORM               ORM
}

type UmbrellaContextValue

type UmbrellaContextValue string

type User

type User struct {
	ID                 int64  `json:"user_id"`
	Flags              int64  `json:"flags"`
	Name               string `json:"name" 2db:"lenmin:0 lenmax:50"`
	Email              string `json:"email" 2db:"req"`
	Password           string `json:"password"`
	EmailActivationKey string `json:"email_activation_key"`
	CreatedAt          int64  `json:"created_at"`
	CreatedBy          int64  `json:"created_by"`
	LastModifiedAt     int64  `json:"last_modified_at"`
	LastModifiedBy     int64  `json:"last_modified_by"`
}

type UserExtraField

type UserExtraField struct {
	Name         string
	RegExp       *regexp.Regexp
	DefaultValue string
}

type UserInterface

type UserInterface interface {
	CreateDBTable() error

	GetID() int64
	GetEmail() string
	SetEmail(string)
	GetPassword() string
	SetPassword(string)
	GetEmailActivationKey() string
	SetEmailActivationKey(string)
	GetFlags() int64
	SetFlags(int64)
	GetExtraField(n string) string
	SetExtraField(n string, v string)

	Save() error
	GetByID(int64) (bool, error)
	GetByEmail(string) (bool, error)
	GetByEmailActivationKey(string) (bool, error)

	GetUser() interface{}
}

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

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