rauther

package module
v1.3.0 Latest Latest
Warning

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

Go to latest
Published: Feb 15, 2022 License: MIT Imports: 24 Imported by: 0

README

Rauther

Rosberry authentification library

Description

Rauther does not have access to the database and does not know how and where you are going to store information about sessions and users. You must implement the required interfaces for the library to work in your code, as well as provide the library with the necessary tools (such as a sms/email/etc sender or an auth type selector). In this case, you can use the standard tools from the library, or you can make your own.

Usage

  1. Implement SessionStorer and UserStorer interfaces for get/save your Session/User model
// SessionStorer interface
type SessionStorer interface {
	// LoadById return Session or create new if not found
	LoadByID(id string) session.Session
	// FindByToken return Session or nil if not found
	FindByToken(token string) session.Session
	Save(session session.Session) error
}

// UserStorer interface
type UserStorer interface {
	// Load return User by uid and auth type or return error if not found.
	LoadByUID(authType, uid string) (user user.User, err error)
	// Load return User by ID or return error if not found.
	LoadByID(id interface{}) (user user.User, err error)
	Create() (user user.User)
	Save(user user.User) error
}

// SocialStorer - optional interface for social user which helps to use information from credentials from social networks for extended user binding
// If the interface is not implemented, then LoadByUID will be used for social user
type SocialStorer interface {
	LoadBySocial(authType string, userDetails user.SocialDetails) (user user.User, err error)
}

// RemovableUserStorer interface (optional, for guest user)
type RemovableUserStorer interface {
	RemoveByID(id interface{}) error
}
  1. Implement Session interface in your Session model
// Session interface
type Session interface {
	GetToken() (token string)
	GetUserID() (userID interface{})

	SetToken(token string)
	// Add relation session <-> user
	BindUser(u user.User)
	// Remove relation session <-> user
	UnbindUser()
}
  1. Implement User (or extendable) interface in your User model.

Rauther consists of several user layers.

  • User. Base layer not use authable modules.
type User interface {
	GetID() (id interface{})
}
  • GuestUser. Guest user layer allows you to create sessions with an already created user but without authorization. Although it is used as a separate layer, when enabled, it changes some behavior in all authorization types.
type GuestUser interface {
	User
	IsGuest() bool
	SetGuest(guest bool)
}
  • AuthableUser. Authable user allows you to authorize a user and use security routes.
type AuthableUser interface {
	User
	GetUID(authType string) (uid string)
	SetUID(authType, uid string)
}

Also rauther consists of 3 authentication modules for AuthableUser:

  • Password (by email, phone, etc)
type PasswordAuthableUser interface {
	AuthableUser
	GetPassword(authType string) (password string)
	SetPassword(authType, password string)
}
  • Social (google, apple, etc)

This interface may not be required for implementation in order for social networks to work, but adds the ability to process user information from social networks.

type SocialAuthableUser interface {
	AuthableUser
	SetUserDetails(authType string, userDetails SocialDetails)
}
  • OTP (one time password)
type OTPAuth interface {
	AuthableUser
	GetOTP(authType string) (code string)
	SetOTP(authType string, code string) error
}

Each needs its own interface. You can implement any number of them. The implementation of these interfaces, as well as other things, allows you to connect special modules. See the description of "Modules".

Also AuthableUser modules make use additional interfaces.

type ConfirmableUser interface {
	AuthableUser
	Confirmed() (ok bool)
	GetConfirmed(authType string) (ok bool)
	GetConfirmCode(authType string) (code string)
	SetConfirmed(authType string, ok bool)
	SetConfirmCode(authType, code string)
}

type RecoverableUser interface {
	AuthableUser
	GetRecoveryCode(authType string) (code string)
	SetRecoveryCode(authType, code string)
}

// Interface for checking the interval during which confirmation codes cannot be sent
type CodeSentTimeUser interface {
	AuthableUser
	GetCodeSentTime(authType string) *time.Time
	SetCodeSentTime(authType string, t *time.Time)
}

type TempUser interface {
	User
	IsTemp() bool
	SetTemp(temp bool)
}
  1. Use the 'auth' tag to match the fields in the model and fields returned in the Fields() request method
type User struct {
	ID uint `json:"id"`

	Username string `auth:"username" json:"username"`
	Password string `json:"password"`

	FirstName string `auth:"fname" json:"first_name"`
	LastName  string `auth:"lname" json:"last_name"`

	RecoveryCode string
}
  1. Implement sender for confirm/recovery
type Sender interface {
	Send(event int, recipient string, message string) error
}

OR use default email sender

import "github.com/rosberry/rauther/sender"
// ...
emailCredentials := sender.EmailCredentials{
	Server:   cfg.Email.Host,
	Port:     cfg.Email.Port,
	From:     cfg.Email.From,
	FromName: cfg.Email.FromName,
	Pass:     cfg.Email.Password,
	Timeout:  timeout * time.Second,
}
emailSender, err := sender.NewDefaultEmailSender(emailCredentials, nil, nil)

Timeout - timeout for connection to email provider.

2nd and 3nd argument - Subjects and messages templates. Uses defaults if not exists. They have a type map[Event]string. Type Event shoulde be either sender.ConfirmationEvent or sender.PasswordRecoveryEvent. For any event you make custom notification. For example see defaultSender

  1. Implement sign-up/sign-in request types
type (
	// AuthRequest is basic sign-up/sign-in interface
	AuthRequest interface {
		GetUID() (uid string)
		GetPassword() (password string)
	}
	// For password module
	CheckUserExistsRequest interface {
		GetUID() (uid string)
	}
	// For social module
	SocialAuthRequest interface {
		GetToken() string
	}
	// AuthRequestFieldable is additional sign-up/sign-in interface for use additional fields
	AuthRequestFieldable interface {
		Fields() map[string]interface{}
	}
)

OR use default struct

type SignUpRequestByEmail struct {
	Email    string `json:"email" form:"email" binding:"required"`
	Password string `json:"password" form:"password" binding:"required"`
}
type CheckLoginFieldRequestByEmail struct {
	Email string `json:"email" form:"email" binding:"required"`
}
type SocialSignInRequest struct {
	Token string `json:"token" binding:"required"`
}
  1. Init gin engine
  2. Init rauther
    rauth := rauther.New(deps.New(
		group, // gin group
		deps.Storage{
			SessionStorer: sessionStorer,
			UserStorer:    userStorer,
		},
	))
  1. Determine one or more auth types.

Rauther may add this with AddAuthMethod for simple adding or AddAuthMethods for adding group. Also we may use chaining with AddAuthMethod (rauth.AddAuthMethod(...).AddAuthMethod(...))

Password module example:

	rauth.AddAuthMethod(authtype.AuthMethod{
		Key:                    "email",
		Type:                   authtype.Password, // by default
		Sender:                 emailSender,
		SignUpRequest:          &models.SignUpRequest{},
		SignInRequest:          &models.SignInRequest{},
		CheckUserExistsRequest: &models.CheckLoginRequest{},
	})

Social module example:

	rauth.AddAuthMethod(authtype.AuthMethod{
		Key:                 "google",
		Type:                authtype.Social,
		Sender:              emailSender,
		SocialSignInRequest: &models.SocialSignInRequest{},
		SocialAuthType:      authtype.SocialAuthTypeGoogle,
	})

OTP module example:

	rauth.AddAuthMethod(authtype.AuthMethod{
		Key:           "otp",
		Type:          authtype.OTP,
		Sender:        emailSender,
		SignUpRequest: &models.OTPSendCodeRequest{},
		SignInRequest: &models.OTPSignInRequest{},
	})

Parameters:

  • Key. Key for ident auth type. For example "email", "phone", "email2", "google", etc.
  • Type. Indicates which of the 3 authorization modules we want to use. Variants: authtype.Password, authtype.Social, authtype.OTP. By default uses authtype.Password.
  • Sender. Parameter from step 5. Sender is object, that can send confirm/recovery code to user. Should implement interface Sender Add default sender, if you want not set sender for auth types
  • SignUpRequest, SignInRequest. This is objects, that will use for sign up/sign in requests. Should implement SignUpRequest interface or extendable (step 6). You can not transmit signUp/signIn request types, then will be use default.
  • CheckUserExistsRequest. Interface for password module.
  1. Set custom selector for auth types [optional]

For example:

	selector := func(c *gin.Context, t authtype.Type) (key string) {
		if t == authtype.Social {
			key = c.Param("type")
		}

		if key != "" {
			return key
		}

		return authtype.DefaultSelector(c, t)
	}

	rauth.AuthSelector(selector)
  • selector - function with type func(c *gin.Context) (senderKey string) for "how select right auth type"
  1. Configure rauther usage Modules and Config

For example:

rauth.Modules.ConfirmableUser = false
rauth.Modules.RecoverableUser = false
rauth.Config.Routes.SignUp = "registration"
rauth.Config.CreateGuestUser = true
rauth.Config.LinkAccount = true
rauth.Config.Password.CodeLifeTime = time.Minute * 30
rauth.Config.OTP.CodeLifeTime = time.Minute * 5

All configs parameters here

  1. Init rauther handlers
err := rauth.InitHandlers()
  1. Run your gin
r.Run()

Modules

Library have some modules for differend work types. modules turn on automatically if all conditions are met. Сonditions are formed from the found implemented interfaces and layers as well as the added types of authorizations in AddAuthMethod. You can turn off each of them manually in step 11.

  • Session - main module ...
  • AuthableUser - module for auth user. Enable handlers ...
  • PasswordAuthableUser - module for enabled password authentication routes
  • SocialAuthableUser - module for enabled social authentication routes
  • One Time Password - module for enabled OTP authentication routes
  • ConfirmableUser - module for require confirm user contact (email, phone, etc). Enable handlers...
  • RecoverableUser - module for recovery user password. Enable handlers...
  • CodeSentTimeUser - module for expired confirmations
  • LinkAccount - module for link account feature. Allows you to create multiple auth identifiers for one user. Use sign-up methods (password sign-up, otp auth, social login) for an authorized user

Examples

Default usage

You can..

Default Example

Client-Server iteraction

Requests-Responses...

Diagrams

diags

About interfaces

userID type negotiation

Rauther has several methods that use the interface{} type userID as arguments. It is important that all these types that will be converted to interface{} are of the same type. For example Session.GetUserID() must return (userID interface{}) which is then transferred to UserStorer.LoadByID(userID interface{}). In GetUserID we just return any type: uint, int, uint32 and other. But when userID comes from arguments, then we must convert it to the same type that we previously sent to the session. For example userID, ok := id.(int). Otherwise, not obvious type errors will occur. Be careful. This applies to such methods: Session.GetUserID,UserStorer.LoadByID,RemovableUserStorer.RemoveByID,User.GetID.

Creating and Saving

In all methods like LoadBy..., Get..., Find... in interfaces is recommended to use only the logic of finding records. It is not recommended to create new records in the database if rows are not found inside methods. This can lead to unexpected consequences and unnecessary database queries.

Also, the method UserStorer.Create should only return the initial data structure without creating any records in the database.

All final saves are expected to be done in methods SessionStorer.Save, UserStorer.Save.

If your user in the database uses external relations with other tables, for example, authIdentities, then sometimes it becomes difficult to get and save these related tables. You can load all dependencies in methods LoadByID, LoadByUID. Therefore, if unloaded dependencies are allowed in the methods LoadBy..., then the methods Set..., Get... for obtaining individual fields will have problems with access to these external fields and nil pointer errors. Accordingly, it is recommended that all changes with external tables be reflected in the total in Save method.

Session.LoadByID assumes that if the session was not found, then it needs to be created in database.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type CustomError added in v1.2.0

type CustomError struct {
	Status   int
	Response common.Err
}

func NewCustomError added in v1.2.0

func NewCustomError(status int, code, message string) CustomError

func (CustomError) Error added in v1.2.0

func (ce CustomError) Error() string

type MergeError added in v1.3.0

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

func (MergeError) Error added in v1.3.0

func (err MergeError) Error() string

func (MergeError) MarshalJSON added in v1.3.0

func (err MergeError) MarshalJSON() ([]byte, error)

type Rauther

type Rauther struct {
	Config  config.Config
	Modules *modules.Modules
	// contains filtered or unexported fields
}

Rauther main object - contains configuration and other details for running.

func New

func New(deps deps.Deps) *Rauther

New make new instance of Rauther with default configuration

func (*Rauther) AddAuthMethod

func (r *Rauther) AddAuthMethod(at authtype.AuthMethod) *Rauther

AddAuthMethod adds a new method of authorization and uses a default sender, if not transmitted another

func (*Rauther) AddAuthMethods

func (r *Rauther) AddAuthMethods(methods []authtype.AuthMethod) *Rauther

AddAuthMethods adds a new types of authorization and uses a default sender, if not transmitted another

func (*Rauther) AfterAuth

func (r *Rauther) AfterAuth(f func(resp gin.H, ses session.Session))

func (*Rauther) AfterAuthCheck added in v1.1.0

func (r *Rauther) AfterAuthCheck(f func(resp gin.H, ses session.Session))

func (*Rauther) AfterOTPSignIn added in v1.2.0

func (r *Rauther) AfterOTPSignIn(f func(resp gin.H, sess session.Session, u user.User, authKey string))

func (*Rauther) AfterOTPSignUp added in v1.2.0

func (r *Rauther) AfterOTPSignUp(f func(resp gin.H, sess session.Session, u user.User, authKey string))

func (*Rauther) AfterPasswordSignIn added in v1.2.0

func (r *Rauther) AfterPasswordSignIn(f func(resp gin.H, sess session.Session, u user.User, authKey string))

func (*Rauther) AfterPasswordSignUp added in v1.2.0

func (r *Rauther) AfterPasswordSignUp(f func(resp gin.H, sess session.Session, u user.User, authKey string))

func (*Rauther) AfterSocialSignIn added in v1.2.0

func (r *Rauther) AfterSocialSignIn(f func(resp gin.H, sess session.Session, u user.User, authKey string))

func (*Rauther) AfterSocialSignUp added in v1.2.0

func (r *Rauther) AfterSocialSignUp(f func(resp gin.H, sess session.Session, u user.User, authKey string))

func (*Rauther) AuthMiddleware

func (r *Rauther) AuthMiddleware() gin.HandlerFunc

AuthMiddleware provide public access to auth middleware

func (*Rauther) AuthSelector

func (r *Rauther) AuthSelector(selector authtype.Selector) *Rauther

AuthSelector specifies the selector with which the type of authorization will be selected

func (*Rauther) AuthUserConfirmedMiddleware

func (r *Rauther) AuthUserConfirmedMiddleware() gin.HandlerFunc

func (*Rauther) AuthUserMiddleware

func (r *Rauther) AuthUserMiddleware() gin.HandlerFunc

func (*Rauther) DefaultSender

func (r *Rauther) DefaultSender(s sender.Sender) *Rauther

DefaultSender set sender for all auth types (if auth type not contain sender)

func (*Rauther) InitHandlers

func (r *Rauther) InitHandlers() error

func (*Rauther) LoadByUID added in v1.2.0

func (r *Rauther) LoadByUID(key, uid string) (user.User, error)

Directories

Path Synopsis
example

Jump to

Keyboard shortcuts

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