authn

package module
v0.0.0-...-5494efd Latest Latest
Warning

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

Go to latest
Published: Dec 18, 2023 License: Apache-2.0 Imports: 15 Imported by: 0

README

authn

Build Status Go Reference Go Report Card codecov

Passwordless, email based authentication with MongoDB store.

STATUS: Working, tested, but API may change (not yet at v1.0.0).

The flow is the following:

  1. A user wants to login. He/she provides his/her email.
  2. A one-time entry code is emailed to him/her by Authenticator.SendEntryCode().
  3. User copies the entry code from the email, which can be verified by Authenticator.VerifyEntryCode().
  4. If the entry code was valid, a Token is presented whose value can be used later to authenticate the user.
  5. Authenticity of a user can be verified by Authenticator.VerifyToken().
  6. The user can be logged out by calling Authenticator.InvalidateToken().

The Authenticator automatically manages user identities. When an entry code verification passes, a user identity (User) is created if one does not yet exist for the email. This user identity (UserID) is attached to and returned with all tokens. A user may have multiple emails, and emails can be changed (Authenticator.SetUserEmails()) without affecting the user's identity.

A user may have multiple valid tokens (multiple sessions). Authenticator.InvalidateToken() only invalidates the given token. Authenticator.Tokens() may be used to query all valid sessions of a user by a token value, or Authenticator.UserTokens() by user ID.

Authenticator uses MongoDB as the persistent store, accessed via the official mongo-go driver.

Documentation

Overview

Package authn provides passwordless, email-based user authentication.

The flow is the following:

  1. A user wants to login. He/she provides his/her email.
  2. A one-time entry code is emailed to him/her by Authenticator.SendEntryCode().
  3. User copies the entry code from the email, which can be verified by Authenticator.VerifyEntryCode().
  4. If the entry code was valid, a Token is presented whose value can be used later to authenticate the user.
  5. Authenticity of a user can be verified by Authenticator.VerifyToken().
  6. The user can be logged out by calling Authenticator.InvalidateToken().

The Authenticator automatically manages user identities. When an entry code verification passes, a user identity (User) is created if one does not yet exist for the email. This user identity (UserID) is attached to and returned with all tokens. A user may have multiple emails, and emails can be changed (Authenticator.SetUserEmails()) without affecting the user's identity.

A user may have multiple valid tokens (multiple sessions). Authenticator.InvalidateToken() only invalidates the given token. Authenticator.Tokens() may be used to query all valid sessions of a user by a token value, or Authenticator.UserTokens() by user ID.

Authenticator uses MongoDB as the persistent store, accessed via the official mongo-go driver.

Index

Constants

View Source
const (
	// DefaultAuthnDBName is the default for Config.AuthnDBName.
	DefaultAuthnDBName = "authn"

	// DefaultTokensCollectionName is the default for Config.TokensCollectionName.
	DefaultTokensCollectionName = "tokens"

	// DefaultUsersCollectionName is the default for Config.UsersCollectionName.
	DefaultUsersCollectionName = "users"

	// DefaultEntryCodeBytes is the default for Config.EntryCodeBytes.
	DefaultEntryCodeBytes = 8

	// DefaultEntryCodeExpiration is the default for Config.EntryCodeExpiration.
	DefaultEntryCodeExpiration = 20 * time.Minute

	// DefaultTokenValueBytes is the default for Config.TokenValueBytes.
	DefaultTokenValueBytes = 24

	// DefaultTokenExpiration is the default for Config.TokenExpiration.
	DefaultTokenExpiration = 6 * 31 * 24 * time.Hour // ~6 months
)
View Source
const DefaultEmailTemplate = `` /* 259-byte string literal not displayed */

DefaultEmailTemplate is the default for Config.EmailTemplate.

Variables

View Source
var (
	// ErrAlreadyVerified indicates an attempt to verify an already
	// verified entry code.
	ErrAlreadyVerified = errors.New("already verified")

	// ErrUnknown indicates that the entry code or token value is unknown.
	ErrUnknown = errors.New("unknown")

	// ErrExpired indicates that the entry code or token has expired.
	ErrExpired = errors.New("expired")
)
View Source
var ErrInvalidEmail = errors.New("invalid email")

ErrInvalidEmail indicates that the provided email address is invalid.

Functions

This section is empty.

Types

type Authenticator

type Authenticator[UserData any] struct {
	// contains filtered or unexported fields
}

Authenticator is the implementation of a passwordless authenticator. It's safe for concurrent use by multiple goroutines.

func NewAuthenticator

func NewAuthenticator[UserData any](
	mongoClient *mongo.Client,
	sendEmail EmailSenderFunc,
	cfg Config,
) *Authenticator[UserData]

NewAuthenticator creates a new Authenticator. This function panics if mongoClient or sendEmail are nil, or if Config.EmailTemplate is provided but is invalid.

func (*Authenticator[UserData]) GetUser

func (a *Authenticator[UserData]) GetUser(ctx context.Context, userID primitive.ObjectID) (user *User[UserData], err error)

GetUser returns the user document for the given ID.

func (*Authenticator[_]) InvalidateToken

func (a *Authenticator[_]) InvalidateToken(ctx context.Context, tokenValue string) (err error)

InvalidateToken invalidates the given token. Should be called when a user wants to log out (only the given session).

If the token value is unknown, ErrUnknown is returned. If the token has expired (or has already been invalidated), ErrExpired is returned.

func (*Authenticator[_]) SendEntryCode

func (a *Authenticator[_]) SendEntryCode(ctx context.Context, email string, client *Client, data map[string]any) (err error)

SendEntryCode sends a one-time entry code to the given email address. Should be called when a user wants to login.

If client is provided, it will be saved as Token.EntryClient, At field filled with current timestamp. If client is nil, EntryClient will not be set.

If email is invalid, ErrInvalidEmail is returned.

data is set as EmailParams.Data, and will be available in the email template. The default email template does not use it, so it may be nil if you use the default email template.

func (*Authenticator[_]) SetUserEmails

func (a *Authenticator[_]) SetUserEmails(ctx context.Context, userID primitive.ObjectID, loweredEmails []string) (err error)

SetUserEmails sets the assigned (lowercased) emails of a user.

Emails must be unique across all users. Attempting to set an email that is already associated to another user will result in an error.

func (*Authenticator[_]) Tokens

func (a *Authenticator[_]) Tokens(ctx context.Context, tokenValue string) (tokens []*Token, err error)

Tokens returns all valid tokens associated with the owner of the given token.

If the token value is unknown, ErrUnknown is returned. If the token has expired (or has already been invalidated), ErrExpired is returned.

func (*Authenticator[_]) UserTokens

func (a *Authenticator[_]) UserTokens(ctx context.Context, userID primitive.ObjectID) (tokens []*Token, err error)

UserTokens returns all valid tokens for the given user. No error is reported if the given user does not exists (tokens will be empty of course).

func (*Authenticator[UserData]) VerifyEntryCode

func (a *Authenticator[UserData]) VerifyEntryCode(ctx context.Context, code string, client *Client, validators ...Validator) (token *Token, err error)

VerifyEntryCode verifies the given entry code. Should be called to verify user's email upon login.

If client is provided, it will be saved as Token.EntryClient, At field filled with current timestamp. If client is nil, EntryClient will not be updated.

If the entry code is unknown, ErrUnknown is returned. If the entry code has expired, ErrExpired is returned.

An entry code can only be verified once. If the entry code is known but has been verified before, ErrAlreadyVerified is returned.

If there are validators passed, they are called before the token is accepted and updated, in the order they are provided, which may veto the decision. If a validation error occurs, an error wrapping that is returned early. If a user exists associated with the token's email at the time of the verification, Token.UserID is set to the existing user's ID. If no such user exists, no user is created when calling the validators.

If validation passes and no user exists associated with the token's email address, a new user is created automatically, and Token.UserID is set to this new user's ID. Changes to the user's email later on will not affect Token.UserID.

func (*Authenticator[_]) VerifyToken

func (a *Authenticator[_]) VerifyToken(ctx context.Context, tokenValue string, client *Client, validators ...Validator) (token *Token, err error)

VerifyToken verifies the given token value. Should be called to verify the authenticity of a logged in user.

If client is provided, it will be saved as Token.Client, At field filled with current timestamp, and Token.Used will also be incremented. If client is nil, Client will not be updated.

If the token value is unknown, ErrUnknown is returned. If the token has expired, ErrExpired is returned.

If there are validators passed, they are called before the token is accepted and updated, in the order they are provided, which may veto the decision. If a validation error occurs, an error wrapping that is returned early.

type Client

type Client struct {
	// User agent of the client.
	UserAgent string `bson:"agent,omitempty"`

	// IP address of the client.
	IP string `bson:"ip,omitempty"`

	// At tells when the token was accessed.
	At time.Time `bson:"at"`

	// Data may hold arbitrary data.
	Data map[string]any `bson:"data,omitempty"`
}

Client holds some information about the client.

type Config

type Config struct {
	// AuthnDBName is the name of the database used by the Authenticator.
	AuthnDBName string

	// TokensCollectionName is the name of the database collection used by the
	// Authenticator to store tokens.
	TokensCollectionName string

	// UsersCollectionName is the name of the database collection used by the
	// Authenticator to store users.
	UsersCollectionName string

	// EntryCodeBytes tells how many bytes to use for entry codes.
	// The actual entry code is a hex string, will be twice as many hex digits.
	EntryCodeBytes int

	// EntryCodeExpiration tells how long an unverified entry code remains valid.
	EntryCodeExpiration time.Duration

	// TokenValueBytes tells how many bytes to use for token values.
	// The actual token string is base64, will be roughly 4/3 times longer.
	TokenValueBytes int

	// TokenExpiration tells how long a token remains valid.
	TokenExpiration time.Duration

	// EmailTemplate is the template text of the emails to be sent out
	// with entry codes.
	EmailTemplate string

	// SiteName is used in the entry code emails.
	// Has no default, should be provided if the default email template is used.
	SiteName string

	// SenderName is used in the entry code emails.
	// Has no default, should be provided if the default email template is used.
	SenderName string
}

Config holds Authenticator configuration. A zero value is a valid configuration, see constants for default values.

type EmailParams

type EmailParams struct {
	Email               string
	SiteName            string
	EntryCode           string
	EntryCodeExpiration time.Duration
	SenderName          string

	// Data may hold custom data.
	// Its value comes from Authenticator.SendEntryCode().
	Data map[string]any
}

EmailParams is passed as data when executing the email template.

type EmailSenderFunc

type EmailSenderFunc func(ctx context.Context, to, body string) error

EmailSenderFunc is the type of the function used to send out emails.

type Token

type Token struct {
	// ID of the token.
	ID primitive.ObjectID `bson:"_id,omitempty"`

	// Case-sensitive email of the owner of the token.
	Email string `bson:"email"`

	// Lowercased email of the owner of the token.
	// Used for lookups.
	LoweredEmail string `bson:"lemail"`

	// Token creation timestamp.
	Created time.Time `bson:"c"`

	// One-time entry code for the token.
	EntryCode string `bson:"ecode"`

	// EntryClient is the client information of the entry code verification.
	EntryClient *Client `bson:"eclient,omitempty"`

	// Verified tells if the token's entry code has been verified.
	Verified bool `bson:"verified"`

	// UserID is the ID of the owner of the token.
	UserID primitive.ObjectID `bson:"userID"`

	// Client information of the last access.
	Client *Client `bson:"client,omitempty"`

	// Expires tells when this entry code or token expires.
	Expires time.Time `bson:"exp"`

	// Reusable token value for authentication.
	Value string `bson:"value"`

	// Used tells how many times this token was used.
	// A token is used when it is verified, and only
	// if Client information is provided.
	Used int `bson:"used,omitempty"`
}

Token represents a token which authenticates users.

func (*Token) Expired

func (t *Token) Expired() bool

Expired tells if this token has expired.

type User

type User[UserData any] struct {
	// ID of the user.
	ID primitive.ObjectID `bson:"_id"`

	// Lowercased emails of the user.
	LoweredEmails []string `bson:"lemails"`

	// User creation timestamp.
	Created time.Time `bson:"c"`

	// Data may hold arbitrary data.
	Data UserData `bson:"data,omitempty"`
}

User represents a user that owns tokens. A user may have multiple emails, and emails may be changed later, the User (identified by its ID) will remain the same.

type Validator

type Validator func(ctx context.Context, token *Token, client *Client) error

Validator is a function which can check a token before it is accepted and updated in Authenticator.VerifyEntryCode() and Authenticator.VerifyToken(). The validator receives the persisted, un-updated token and the new client passed to the above functions.

Validators may be used to perform extensive checks on the client, e.g. check and restrict IP addresses or disallow changed user agents.

Jump to

Keyboard shortcuts

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