gomagiclink

package module
v0.9.2 Latest Latest
Warning

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

Go to latest
Published: Jul 3, 2024 License: MIT Imports: 11 Imported by: 0

README

Go Reference

Magic Link authentication framework for Go. The idea is to avoid asking the user for the password every time they login, and send them a "magic link" to their e-mail address. Once they confirm the login by clicking the link, the user is considered logged in. This has the advantage of not handling user passwords at all, and the disadvantage that if their e-mail account is compromised, this will cascade to services dependant on the e-mail account, such as ones implementing the magic link login.

This package implements the core part of this process, by generating a cryptographically safe magic link challenge and a session id (useful for web cookies). To keep the process safe, you need to maintain the security of the secret key, passed to the NewAuthMagicLinkController() function.

Design decisions

  • We don't write down information about the user until they verify the challenge
  • We don't write down session information at all, but verify the cookie JWT-style
  • We allow the app to attach arbitrary data about a user, and store it with the user record
  • We allow easy implementation of different data stores

Workflows

See these examples for more info:

Registration / Login

  • Construct an AuthUserDatabase
  • Construct an AuthMagicLinkController
  • Collect user e-mail (web form, etc)
  • Generate a challenge string (magic cookie) with GenerateChallenge(), construct a link with it and send it to user's e-mail
  • Verify the challenge with VerifyChallenge(). If successful, it will return an UserAuthRecord
  • Optionally attach custom user data to the CustomData field of the record and store the AuthUserRecord with StoreUser(). Note that this data will be stored and retrieved as JSON, so the CustomData needs to be of a type that can survive a round-trip through JSON. For example, ints will be returned as float64s.

Session

  • Generate a session ID with GenerateSessionId(), send to browser, e.g. as a HTTP cookie, or a Bearer token
  • Each time the browser sends back the session ID, verify it with VerifySessionId(). It will return an AuthUserRecord if successful. Inspect the CustomData field if you've set it before.

Sending e-mail

Configuring an e-mail server, etc. is waaaay out of scope for this package, but here's a good e-mail library for Go.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrBrokenChallenge = errors.New("broken challenge")
View Source
var ErrBrokenSessionId = errors.New("broken session id")
View Source
var ErrExpiredChallenge = errors.New("expired challenge")
View Source
var ErrExpiredSessionId = errors.New("expired session id")
View Source
var ErrInvalidChallenge = errors.New("invalid challenge")
View Source
var ErrInvalidSessionId = errors.New("invalid session id")
View Source
var ErrSecretKeyTooShort = errors.New("secret Key too short (min 16 bytes)")
View Source
var ErrUserAlreadyExists = errors.New("user already exists")
View Source
var ErrUserDisabled = errors.New("user disabled")
View Source
var ErrUserNotFound = errors.New("user not found")

Functions

func IsZeroULID

func IsZeroULID(u ulid.ULID) bool

func NormalizeEmail

func NormalizeEmail(email string) string

Types

type AuthMagicLinkController

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

All functionalities needed to implement the Magic Link login system is available through the AuthMagicLinkController.

func NewAuthMagicLinkController

func NewAuthMagicLinkController(secretKey []byte, challengeExpDuration time.Duration, sessionExpDuration time.Duration, db UserAuthDatabase) (mlc *AuthMagicLinkController, err error)

NewAuthMagicLinkController configures and creates a new instance of the AuthMagicLinkController. The secretKey needs to be kept safe. To provide your own storage mechanism for the magic link data, implement the UserAuthDatabase interface. There are file system and SQL database implementations provided.

func (*AuthMagicLinkController) GenerateChallenge

func (mlc *AuthMagicLinkController) GenerateChallenge(email string) (challenge string, err error)

GenerateChallenge creates a challenge string to be used for constructing the magic link. This challenge string needs to be verified by VerifyChallenge()

func (*AuthMagicLinkController) GenerateSessionId

func (mlc *AuthMagicLinkController) GenerateSessionId(user *AuthUserRecord) (sessionId string, err error)

GenerateSessionId generates a session id suitable for using as a cookie in a web app.

func (*AuthMagicLinkController) GetUserByEmail

func (mlc *AuthMagicLinkController) GetUserByEmail(email string) (*AuthUserRecord, error)

func (*AuthMagicLinkController) StoreUser

func (mlc *AuthMagicLinkController) StoreUser(user *AuthUserRecord) error

func (*AuthMagicLinkController) UserExistsByEmail

func (mlc *AuthMagicLinkController) UserExistsByEmail(email string) bool

func (*AuthMagicLinkController) VerifyChallenge

func (mlc *AuthMagicLinkController) VerifyChallenge(challenge string) (user *AuthUserRecord, err error)

VerifyChallenge verifies the challenge string generated by GenerateChallenge(), and returns the AuthUserRecord corresponding to the user for which the challenge was created (identifying them by their email address).

func (*AuthMagicLinkController) VerifySessionId

func (mlc *AuthMagicLinkController) VerifySessionId(sessionId string) (user *AuthUserRecord, err error)

VerifySessionId verifies the session ID generated by GenerateSessionId() and if it's valid, returns the AuthUserRecord of the associated user.

type AuthUserRecord

type AuthUserRecord struct {
	ID              ulid.ULID `json:"id"`    // Unique identifier
	Email           string    `json:"email"` // Also must be unique
	Enabled         bool      `json:"enabled"`
	FirstLoginTime  time.Time `json:"first_login_time"`
	RecentLoginTime time.Time `json:"recent_login_time"`
	CustomData      any       `json:"custom_data"` // Apps can attach any kind of custom data to the user record
}

AuthUser represents user data

func NewAuthUserRecord

func NewAuthUserRecord(email string) (aur *AuthUserRecord, err error)

NewAuthUserRecords constructs a new AuthUserRecord. This function isn't normally directly called by the users of this package.

func (*AuthUserRecord) GetID

func (aur *AuthUserRecord) GetID() ulid.ULID

Returns the user ID.

func (*AuthUserRecord) GetKeyName

func (aur *AuthUserRecord) GetKeyName() string

Returns the Key name suitable for key-value databases.

type RecordWithID

type RecordWithID interface {
	GetID() ulid.ULID
}

type RecordWithKeyName

type RecordWithKeyName interface {
	GetKeyName() string
}

type UserAuthDatabase

type UserAuthDatabase interface {
	UserExistsByEmail(email string) bool
	StoreUser(user *AuthUserRecord) error
	GetUserById(id ulid.ULID) (*AuthUserRecord, error)
	GetUserByEmail(email string) (*AuthUserRecord, error)
}

When a new storage provider is created, it implements this interface. See the provided storage provided in the `storage` package.

Directories

Path Synopsis
cmd
demo command
webdemo command

Jump to

Keyboard shortcuts

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