auth

package
v0.0.0-...-6b846f9 Latest Latest
Warning

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

Go to latest
Published: Jan 15, 2021 License: Apache-2.0 Imports: 30 Imported by: 0

Documentation

Index

Constants

View Source
const (
	Length                 = 32
	DefaultRefreshTokenTTL = 24 * time.Hour
)
View Source
const (
	SRevokedByLogout uint32 = 1 << iota
	SRevokedByExpiry
	SRevokedBySystem
	SRevokedByClient
	SCreatedByCreds
	SCreatedByRefToken
)
View Source
const (
	// DefaultIdleTimeoutSec is the time since the last session activity (in seconds)
	DefaultIdleTimeoutSec = 120

	// DefaultSessionTTL is the default session lifetime
	DefaultSessionTTL = 10 * time.Minute
)
View Source
const (
	IKNone = iota
	IKUser
	IKApplication
)

Variables

View Source
var (
	ErrNilUserManager                  = errors.New("user manager is nil")
	ErrNilPrivateKey                   = errors.New("private key is nil")
	ErrAuthenticationFailed            = errors.New("authentication failed")
	ErrNilAuthenticator                = errors.New("authenticator is nil")
	ErrEmptyUsername                   = errors.New("username is empty")
	ErrEmptyPassword                   = errors.New("password is empty")
	ErrUserSuspended                   = errors.New("user is suspended")
	ErrInvalidAccessToken              = errors.New("invalid accesspolicy token")
	ErrInvalidRefreshToken             = errors.New("invalid refresh token")
	ErrInvalidRefreshTokenID           = errors.New("refresh token id is invalid")
	ErrRefreshTokenExpired             = errors.New("refresh token has expired")
	ErrRefreshTokenNotActive           = errors.New("refresh token is not active")
	ErrRefreshTokenRotated             = errors.New("refresh token is rotated")
	ErrRefreshTokenRevoked             = errors.New("refresh token is revoked")
	ErrIPAddrMismatch                  = errors.New("ip address mismatch")
	ErrIdentityMismatch                = errors.New("identity mismatch")
	ErrUserAgentMismatch               = errors.New("user agent mismatch")
	ErrNilSession                      = errors.New("session is nil")
	ErrSessionNotFound                 = errors.New("session not found")
	ErrSessionRevoked                  = errors.New("session is revoked")
	ErrSessionAlreadyRevoked           = errors.New("session is already revoked")
	ErrInvalidRevocationFlag           = errors.New("invalid revocation flag")
	ErrInvalidIdentityID               = errors.New("invalid identity id")
	ErrRefreshTokenNotFound            = errors.New("refresh token not found")
	ErrInvalidJTI                      = errors.New("invalid access token id")
	ErrInvalidSessionID                = errors.New("invalid session id")
	ErrInvalidExpirationTime           = errors.New("invalid expiration time")
	ErrZeroExpiration                  = errors.New("expiration time is zero")
	ErrRefreshTokenIsEmpty             = errors.New("refresh token is empty")
	ErrNilPasswordManager              = errors.New("password manager is nil")
	ErrAuthorizationCodeNotFound       = errors.New("authorization code not found")
	ErrClientDisabled                  = errors.New("client is disabled")
	ErrClientExpired                   = errors.New("client is expired")
	ErrClientSecretMismatch            = errors.New("client secret mismatch")
	ErrClientIsNonconfidential         = errors.New("client is non-confidential")
	ErrEmptyCodeChallenge              = errors.New("code challenge is empty")
	ErrEmptyCodeChallengeMethod        = errors.New("code challenge method is empty")
	ErrInvalidCodeChallengeMethod      = errors.New("code challenge method is invalid")
	ErrCodeChallengeVerificationFailed = errors.New("code challenge verification failed")
	ErrInvalidTraceID                  = errors.New("invalid trace id")
	ErrAuthorizationCodeEmpty          = errors.New("authorization code is empty")
	ErrEntryNotFound                   = errors.New("entry not found")
)

errors

View Source
var EmptyRefreshTokenHash = RefreshTokenHash{}

EmptyRefreshTokenHash is a predefined sample for comparison

View Source
var NilIdentity = Identity{
	ID:   uuid.UUID{},
	Kind: 0,
}

NilIdentity represents a "no identity"

Functions

func NewAccessToken

func NewAccessToken(
	privateKey *rsa.PrivateKey,
	jti uuid.UUID,
	ident Identity,
	expireAt time.Time,
) (signedToken string, err error)

Types

type Authenticator

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

Authenticator represents an authenticator which is responsible for the user authentication and authorization

func NewAuthenticator

func NewAuthenticator(
	pk *rsa.PrivateKey,
	users *user.Manager,
	clients *client.Manager,
	b Backend,
	opts Options,
) (*Authenticator, error)

NewAuthenticator initializes a new authenticator NOTE: if private key is nil, then using an autogenerated key

func (*Authenticator) AuthenticateClientBySecret

func (a *Authenticator) AuthenticateClientBySecret(
	ctx context.Context,
	clientID uuid.UUID,
	secret []byte,
	meta *RequestMetadata,
) (
	c *client.Client,
	session *Session,
	signedToken string,
	err error,
)

TODO take metadata into account when authenticating

func (*Authenticator) AuthenticateUserByPassword

func (a *Authenticator) AuthenticateUserByPassword(
	ctx context.Context,
	username string,
	rawpass []byte,
	meta *RequestMetadata,
) (u user.User, err error)

AuthenticateByPassword authenticates a user by username and password. each successful authentication generates a new access token and spawns a new session identified by that token's JTI (JWT ID). NOTE: the number of active sessions should be limited by a sensible amount

func (*Authenticator) AuthenticateUserByRefreshToken

func (a *Authenticator) AuthenticateUserByRefreshToken(
	ctx context.Context,
	c *client.Client,
	hash RefreshTokenHash,
	meta *RequestMetadata,
) (
	session *Session,
	tpair TokenPair,
	err error,
)

func (*Authenticator) CreateAuthorizationCode

func (a *Authenticator) CreateAuthorizationCode(
	ctx context.Context,
	challenge PKCEChallenge,
	tpair TokenPair,
) (code string, err error)

func (*Authenticator) CreateSession

func (a *Authenticator) CreateSession(
	ctx context.Context,
	traceID uuid.UUID,
	clnt *client.Client,
	ident Identity,
	meta *RequestMetadata,
) (
	session *Session,
	signedToken string,
	err error,
)

CreateSession creates a new session for a given owner within the specified realm

func (*Authenticator) CreateSessionWithRefreshToken

func (a *Authenticator) CreateSessionWithRefreshToken(
	ctx context.Context,
	traceID uuid.UUID,
	oldToken *RefreshToken,
	clnt *client.Client,
	ident Identity,
	meta *RequestMetadata,
) (
	session *Session,
	tpair TokenPair,
	err error,
)

func (*Authenticator) ExchangeAuthorizationCode

func (a *Authenticator) ExchangeAuthorizationCode(
	ctx context.Context,
	code string,
	verifier string,
) (
	tpair TokenPair,
	err error,
)

func (*Authenticator) GroupManager

func (a *Authenticator) GroupManager() *group.Manager

func (*Authenticator) Logger

func (a *Authenticator) Logger() *zap.Logger

Logger returns own logger

func (*Authenticator) PasswordManager

func (a *Authenticator) PasswordManager() password.Manager

func (*Authenticator) PrivateKey

func (a *Authenticator) PrivateKey() (*rsa.PrivateKey, error)

PrivateKey returns a private key (RSA) used by this manager

func (*Authenticator) RefreshTokenByHash

func (a *Authenticator) RefreshTokenByHash(
	ctx context.Context,
	hash RefreshTokenHash,
) (
	rt RefreshToken,
	err error,
)

TODO: considered whether this is a redundant function

func (*Authenticator) RevokeRefreshToken

func (a *Authenticator) RevokeRefreshToken(
	ctx context.Context,
	hash RefreshTokenHash,
	reason string,
) (err error)

func (*Authenticator) RevokeSession

func (a *Authenticator) RevokeSession(ctx context.Context, sessionID uuid.UUID, revoker uint32, reason string) (err error)

RevokeSession revokes existing session instead of deleting, because previously issued access tokens are still out and can be used to re-generate a new session, thus it must be preserved until it eventually expires, and then deleted WARNING: session MUST NOT be deleted at least until it expires naturally

func (*Authenticator) RotateRefreshToken

func (a *Authenticator) RotateRefreshToken(ctx context.Context, tokenHash RefreshTokenHash) (newToken RefreshToken, err error)

func (*Authenticator) SessionByAccessToken

func (a *Authenticator) SessionByAccessToken(ctx context.Context, signedToken string) (session *Session, err error)

func (*Authenticator) SessionByID

func (a *Authenticator) SessionByID(ctx context.Context, jti uuid.UUID) (*Session, error)

SessionByID returns a session if it's found in the backend by its token string

func (*Authenticator) SetLogger

func (a *Authenticator) SetLogger(logger *zap.Logger) error

SetLogger sets an own logger

func (*Authenticator) TokenManager

func (a *Authenticator) TokenManager() *token.Manager

func (*Authenticator) UserManager

func (a *Authenticator) UserManager() *user.Manager

type AuthorizationCodePayload

type AuthorizationCodePayload struct {
	PKCEChallenge `json:"pkce"`
	TokenPair     `json:"token_pair"`
}

type Backend

type Backend interface {
	CreateSession(ctx context.Context, s *Session) (err error)
	GetHeadSessionByTraceID(ctx context.Context, traceID uuid.UUID) (session *Session, err error)
	UpdateSession(ctx context.Context, id uuid.UUID, fn func(ctx context.Context, session *Session) (*Session, error)) (session *Session, err error)
	DeleteSession(ctx context.Context, s *Session) (err error)
	CreateRefreshToken(ctx context.Context, rtok RefreshToken) (err error)
	GetRefreshTokenByHash(ctx context.Context, hash RefreshTokenHash) (rtok RefreshToken, err error)
	GetHeadRefreshTokenByTraceID(ctx context.Context, traceID uuid.UUID) (rtok RefreshToken, err error)
	UpdateRefreshToken(ctx context.Context, h RefreshTokenHash, fn func(ctx context.Context, rtok RefreshToken) (RefreshToken, error)) (rtok RefreshToken, err error)
	DeleteRefreshToken(ctx context.Context, rtok RefreshToken) (err error)
	CreateAuthorizationCode(ctx context.Context, code string, challenge PKCEChallenge, tpair TokenPair) (err error)
	GetAuthorizationPayloadByCode(ctx context.Context, code string) (payload AuthorizationCodePayload, err error)
	DeleteAuthorizationCode(ctx context.Context, code string) (err error)
	GetSessionByID(ctx context.Context, jti uuid.UUID) (*Session, error)
}

Backend is an interface contract for an auth backend

type Cache

type Cache interface {
	Put(ctx context.Context, key string, entry []byte) (err error)
	Get(ctx context.Context, key string) (entry []byte, err error)
	Take(ctx context.Context, key string) (entry []byte, err error)
	Delete(ctx context.Context, key string) (err error)
}

func NewDefaultCache

func NewDefaultCache(config bigcache.Config) (Cache, error)

type Claims

type Claims struct {
	Identity Identity `json:"identity"`
	jwt.StandardClaims
}

Claims holds required JWT claims

type ClientCredentials

type ClientCredentials struct {
	ClientID uuid.UUID  `json:"client_id"`
	Secret   token.Hash `json:"secret"`
}

ClientCredentials represents client authentication credentials

type Context

type Context struct {
	Identity
	Domain
}

Context holds metadata which describes authenticated session

type ContextKey

type ContextKey uint8

ContextKey is a named context key type for this package

const (
	CKAuthenticator ContextKey = iota
	CKSession
	CKUserManager
	CKUser
	CKClient
)

context keys

type Credentials

type Credentials struct {
	Client *ClientCredentials
	User   *UserCredentials
}

type DefaultBackend

type DefaultBackend struct {
	sync.RWMutex
	// contains filtered or unexported fields
}

DefaultBackend is a default in-memory implementation

func NewDefaultRegistryBackend

func NewDefaultRegistryBackend() *DefaultBackend

NewDefaultRegistryBackend initializes a default in-memory registry

func (*DefaultBackend) CreateAuthorizationCode

func (b *DefaultBackend) CreateAuthorizationCode(ctx context.Context, code string, challenge PKCEChallenge, tpair TokenPair) (err error)

func (*DefaultBackend) CreateRefreshToken

func (b *DefaultBackend) CreateRefreshToken(ctx context.Context, rt RefreshToken) error

func (*DefaultBackend) CreateSession

func (b *DefaultBackend) CreateSession(ctx context.Context, session *Session) error

UpsertSession stores a given session to a temporary registry backend

func (*DefaultBackend) DeleteAuthorizationCode

func (b *DefaultBackend) DeleteAuthorizationCode(ctx context.Context, code string) (err error)

func (*DefaultBackend) DeleteRefreshToken

func (b *DefaultBackend) DeleteRefreshToken(ctx context.Context, t RefreshToken) error

DeleteSession deletes a given session from the backend registry

func (*DefaultBackend) DeleteSession

func (b *DefaultBackend) DeleteSession(ctx context.Context, s *Session) error

DeleteSession deletes a given session from the backend registry

func (*DefaultBackend) GetAuthorizationPayloadByCode

func (b *DefaultBackend) GetAuthorizationPayloadByCode(ctx context.Context, code string) (payload AuthorizationCodePayload, err error)

func (*DefaultBackend) GetHeadRefreshTokenByTraceID

func (b *DefaultBackend) GetHeadRefreshTokenByTraceID(ctx context.Context, traceID uuid.UUID) (rtok RefreshToken, err error)

func (*DefaultBackend) GetHeadSessionByTraceID

func (b *DefaultBackend) GetHeadSessionByTraceID(ctx context.Context, traceID uuid.UUID) (_ *Session, err error)

func (*DefaultBackend) GetRefreshTokenByHash

func (b *DefaultBackend) GetRefreshTokenByHash(ctx context.Context, hash RefreshTokenHash) (t RefreshToken, err error)

GetSessionByRefreshToken retrieves session by a refresh token

func (*DefaultBackend) GetSessionByID

func (b *DefaultBackend) GetSessionByID(ctx context.Context, jti uuid.UUID) (s *Session, err error)

GetSession fetches a session by a given token hash from the registry backend

func (*DefaultBackend) RotateRefreshToken

func (b *DefaultBackend) RotateRefreshToken(ctx context.Context, h RefreshTokenHash, newToken RefreshToken) (err error)

func (*DefaultBackend) UpdateRefreshToken

func (b *DefaultBackend) UpdateRefreshToken(
	ctx context.Context,
	h RefreshTokenHash,
	fn func(ctx context.Context, rtok RefreshToken) (RefreshToken, error),
) (rtok RefreshToken, err error)

func (*DefaultBackend) UpdateSession

func (b *DefaultBackend) UpdateSession(
	ctx context.Context,
	id uuid.UUID,
	fn func(ctx context.Context, session *Session) (*Session, error),
) (_ *Session, err error)

type Domain

type Domain uint8

Domain represents an accesspolicy domain

const (
	DGeneral Domain = 1 << iota
	DAdmin
)

func (Domain) String

func (d Domain) String() string

type Identity

type Identity struct {
	ID   uuid.UUID    `json:"id"`
	Kind IdentityKind `json:"kind"`
}

Identity represents a session owner

func UserIdentity

func UserIdentity(id uuid.UUID) Identity

func (Identity) Validate

func (ident Identity) Validate() error

type IdentityKind

type IdentityKind uint8

IdentityKind represents an identity kind (i.e.: user/device/etc...)

func (IdentityKind) String

func (k IdentityKind) String() string

type Options

type Options struct {
	AccessTokenTTL   time.Duration
	RefreshTokenTTL  time.Duration
	CompareIP        bool
	CompareUserAgent bool
}

func DefaultOptions

func DefaultOptions() Options

type PKCEChallenge

type PKCEChallenge struct {
	Challenge string
	Method    string
}

func NewPKCEChallenge

func NewPKCEChallenge(challenge string, method string) (c PKCEChallenge, err error)

func (*PKCEChallenge) Validate

func (c *PKCEChallenge) Validate() error

func (*PKCEChallenge) Verify

func (c *PKCEChallenge) Verify(verifier string) bool

type RefreshToken

type RefreshToken struct {
	ID                     uuid.UUID        `db:"id" json:"id"`
	TraceID                uuid.UUID        `db:"trace_id" json:"trace_id"`
	ParentID               uuid.UUID        `db:"parent_id" json:"parent_id"`
	RotatedID              uuid.UUID        `db:"rotated_id" json:"rotated_id"`
	LastSessionID          uuid.UUID        `db:"last_session_id" json:"last_session_id"`
	PreviousRefreshTokenID uuid.UUID        `db:"previous_refresh_token_id" json:"previous_refresh_token_id"`
	ClientID               uuid.UUID        `db:"client_id" json:"client_id"`
	Identity               Identity         `db:"identity" json:"identity"`
	Hash                   RefreshTokenHash `db:"hash" json:"hash"`
	CreatedAt              time.Time        `db:"created_at" json:"created_at"`
	RotatedAt              time.Time        `db:"rotated_at" json:"rotated_at"`
	RevokedAt              time.Time        `db:"revoked_at" json:"revoked_at"`
	ExpireAt               time.Time        `db:"expire_at" json:"expire_at"`
	Flags                  uint8            `db:"flags" json:"flags"`
	// contains filtered or unexported fields
}

func NewRefreshToken

func NewRefreshToken(
	traceID uuid.UUID,
	jti uuid.UUID,
	c *client.Client,
	identity Identity,
	expireAt time.Time,
) (rtok RefreshToken, err error)

func NewRotatedRefreshToken

func NewRotatedRefreshToken(currentToken RefreshToken) (newToken RefreshToken, err error)

func (*RefreshToken) IsActive

func (rtok *RefreshToken) IsActive() (bool, error)

func (*RefreshToken) IsExpired

func (rtok *RefreshToken) IsExpired() bool

func (*RefreshToken) IsRevoked

func (rtok *RefreshToken) IsRevoked() bool

func (*RefreshToken) IsRotated

func (rtok *RefreshToken) IsRotated() bool

func (*RefreshToken) Validate

func (rtok *RefreshToken) Validate() error

type RefreshTokenHash

type RefreshTokenHash [Length]byte

RefreshTokenHash represents the very secret essence of a refresh token TODO: implement hash encryption for the store TODO: implement hash signature and verification

func NewTokenHash

func NewTokenHash() (hash RefreshTokenHash)

func (RefreshTokenHash) DecodeBinary

func (h RefreshTokenHash) DecodeBinary(ci *pgtype.ConnInfo, src []byte) error

func (RefreshTokenHash) EncodeBinary

func (h RefreshTokenHash) EncodeBinary(ci *pgtype.ConnInfo, buf []byte) (newBuf []byte, err error)

func (RefreshTokenHash) String

func (h RefreshTokenHash) String() string

func (RefreshTokenHash) Validate

func (h RefreshTokenHash) Validate() error

type RequestMetadata

type RequestMetadata struct {
	UserAgent string
	IP        net.IP
}

RequestMetadata holds request information

func NewRequestMetadata

func NewRequestMetadata(r *http.Request) (meta *RequestMetadata)

NewRequestMetadata initializes RequestMetadata from a given http.Request

type Session

type Session struct {
	// ID is also used as JTI (JWT ID)
	ID uuid.UUID `db:"id" json:"id"`

	TraceID uuid.UUID `db:"trace_id" json:"trace_id"`

	// ClientID is an ID of a client that originally initiated this session
	ClientID uuid.UUID `db:"client_id" json:"client_id"`

	// Identity is the owner of this session
	Identity Identity `db:"identity" json:"identity"`

	// IP is the IP address from which this session has been initiated
	IP net.IP `db:"ip" json:"ip"`

	// Flags describes metadata like whether it's idling, revoked,
	// revoked by its owner, expiry, client or some external system
	Flags uint32 `db:"flags" json:"flags"`

	// times
	CreatedAt   time.Time `db:"created_at" json:"created_at"`
	RefreshedAt time.Time `db:"refreshed_at" json:"refreshed_at"`
	RevokedAt   time.Time `db:"revoked_at" json:"revoked_at"`
	ExpireAt    time.Time `db:"expire_at" json:"expire_at"`

	// revocation reason
	RevokeReason string `db:"revoke_reason" json:"revoke_reason"`

	sync.RWMutex
	// contains filtered or unexported fields
}

Session represents an authenticated session

func NewSession

func NewSession(
	traceID uuid.UUID,
	c *client.Client,
	ident Identity,
	meta *RequestMetadata,
	ttl time.Duration,
) (
	s *Session,
	err error,
)

func (*Session) IsCreatedByCredentials

func (s *Session) IsCreatedByCredentials() bool

func (*Session) IsCreatedByRefToken

func (s *Session) IsCreatedByRefToken() bool

func (*Session) IsExpired

func (s *Session) IsExpired() bool

func (*Session) IsIdle

func (s *Session) IsIdle() bool

func (*Session) IsRevoked

func (s *Session) IsRevoked() bool

func (*Session) IsRevokedByClient

func (s *Session) IsRevokedByClient() bool

func (*Session) IsRevokedByExpiry

func (s *Session) IsRevokedByExpiry() bool

func (*Session) IsRevokedByLogout

func (s *Session) IsRevokedByLogout() bool

func (*Session) IsRevokedBySystem

func (s *Session) IsRevokedBySystem() bool

func (*Session) IsValid

func (s *Session) IsValid() bool

func (*Session) LastActiveAt

func (s *Session) LastActiveAt() time.Time

LastActiveAt returns the time when this client was active last time

func (*Session) RevokeByClient

func (s *Session) RevokeByClient() error

func (*Session) RevokeByExpiry

func (s *Session) RevokeByExpiry() error

func (*Session) RevokeBySystem

func (s *Session) RevokeBySystem(reason string) error

func (*Session) TimeLeft

func (s *Session) TimeLeft() time.Duration

TimeLeft returns the time remaining before it expires NOTE: in nanoseconds

func (*Session) Touch

func (s *Session) Touch()

func (*Session) Validate

func (s *Session) Validate() error

SanitizeAndValidate validates the session

type TokenPair

type TokenPair struct {
	AccessToken  string           `json:"access_token"`
	RefreshToken RefreshTokenHash `json:"refresh_token,omitempty"`
}

TokenPair contains access and refresh tokens which are returned back to the client upon successful authentication

type UserCredentials

type UserCredentials struct {
	Username string `json:"username"`
	Password []byte `json:"password"`
}

UserCredentials represents user authentication credentials

func (*UserCredentials) SanitizeAndValidate

func (c *UserCredentials) SanitizeAndValidate() error

SanitizeAndValidate performs basic trimming and validations TODO: consider preserving original username but still change case to lower NOTE: trimming passwords whitespace to prevent problems when people copy+paste

Directories

Path Synopsis
provider

Jump to

Keyboard shortcuts

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