Documentation ¶
Overview ¶
`go-passwordless` is an implementation of backend services allowing users to sign in to websites without a password, inspired by the [Node package of the same name](passwordless.net).
Install the library with `go get`:
$ go get github.com/johnsto/go-passwordless
Import the library into your project:
import "github.com/johnsto/go-passwordless"
Create an instance of Passwordless with your chosen token store. In this case, `MemStore` will hold tokens in memory until they expire.
pw = passwordless.New(passwordless.NewMemStore())
Then add a transport strategy that describes how to send a token to the user. In this case we're using the `LogTransport` which simply writes the token to the console for testing purposes. It will be registered under the name "log".
pw.SetTransport("log", passwordless.LogTransport{ MessageFunc: func(token, uid string) string { return fmt.Sprintf("Your PIN is %s", token) }, }, passwordless.NewCrockfordGenerator(8), 30*time.Minute)
When the user wants to sign in, get a list of valid transports with `passwordless.ListTransports`, and display an appropriate form to the user. You can then send a token to the user:
strategy := r.FormValue("strategy") recipient := r.FormValue("recipient") user := Users.Find(recipient) err := pw.RequestToken(ctx, strategy, user.ID, recipient)
Then prompt the user to enter the token they received:
token := r.FormValue("token") uid := r.FormValue("uid") valid, err := pw.VerifyToken(ctx, uid, token)
If `valid` is `true`, the user can be considered authenticated and the login process is complete. At this point, you may want to set a secure session cookie to keep the user logged in.
A complete implementation can be found in the "example" directory.
Index ¶
- Variables
- func RequestToken(ctx context.Context, s TokenStore, t Strategy, uid, recipient string) error
- func SetContext(ctx context.Context, rw http.ResponseWriter, r *http.Request) context.Context
- func VerifyToken(ctx context.Context, s TokenStore, uid, token string) (bool, error)
- type ByteGenerator
- type ComposerFunc
- type CookieStore
- func (s *CookieStore) Delete(ctx context.Context, uid string) error
- func (s *CookieStore) Exists(ctx context.Context, uid string) (bool, time.Time, error)
- func (s *CookieStore) Store(ctx context.Context, token, uid string, ttl time.Duration) error
- func (s *CookieStore) Verify(ctx context.Context, pin, uid string) (bool, error)
- type CrockfordGenerator
- type Email
- type LogTransport
- type MemStore
- func (s *MemStore) Clean()
- func (s *MemStore) Delete(ctx context.Context, uid string) error
- func (s *MemStore) Exists(ctx context.Context, uid string) (bool, time.Time, error)
- func (s *MemStore) Release()
- func (s *MemStore) Store(ctx context.Context, token, uid string, ttl time.Duration) error
- func (s *MemStore) Verify(ctx context.Context, token, uid string) (bool, error)
- type PINGenerator
- type Passwordless
- func (p *Passwordless) GetStrategy(ctx context.Context, name string) (Strategy, error)
- func (p *Passwordless) ListStrategies(ctx context.Context) map[string]Strategy
- func (p *Passwordless) RequestToken(ctx context.Context, s, uid, recipient string) error
- func (p *Passwordless) SetStrategy(name string, s Strategy)
- func (p *Passwordless) SetTransport(name string, t Transport, g TokenGenerator, ttl time.Duration) Strategy
- func (p *Passwordless) VerifyToken(ctx context.Context, uid, token string) (bool, error)
- type RedisStore
- func (s RedisStore) Delete(ctx context.Context, uid string) error
- func (s RedisStore) Exists(ctx context.Context, uid string) (bool, time.Time, error)
- func (s RedisStore) Store(ctx context.Context, token, uid string, ttl time.Duration) error
- func (s RedisStore) Verify(ctx context.Context, token, uid string) (bool, error)
- type SMTPTransport
- type SimpleStrategy
- type Strategy
- type TokenGenerator
- type TokenStore
- type Transport
Constants ¶
This section is empty.
Variables ¶
var ( ErrNoStore = errors.New("no store has been configured") ErrNoTransport = errors.New("no transports have been configured") ErrUnknownStrategy = errors.New("unknown strategy") ErrNotValidForContext = errors.New("strategy not valid for context") )
var ( ErrTokenNotFound = errors.New("the token does not exist") ErrTokenNotValid = errors.New("the token is incorrect") )
Functions ¶
func RequestToken ¶
RequestToken generates, saves and delivers a token to the specified recipient.
func SetContext ¶
SetContext returns a Context containing the specified `ResponseWriter` and `Request`. If a nil Context is provided, a new one is returned.
func VerifyToken ¶
VerifyToken checks the given token against the provided token store.
Types ¶
type ByteGenerator ¶
ByteGenerator generates random sequences of bytes from the specified set of the specified length.
func NewByteGenerator ¶
func NewByteGenerator(b []byte, l int) *ByteGenerator
NewByteGenerator creates and returns a ByteGenerator.
type ComposerFunc ¶
ComposerFunc is called when writing the contents of an email, including preamble headers.
type CookieStore ¶
CookieStore stores tokens in a encrypted cookie on the user's browser. This token is then decrypted and checked against the provided value to determine of the token is valid.
func NewCookieStore ¶
func NewCookieStore(signingKey, authKey, encrKey []byte) *CookieStore
NewCookieStore creates a new signed and encrypted CookieStore.
func (*CookieStore) Delete ¶
func (s *CookieStore) Delete(ctx context.Context, uid string) error
Delete deletes the cookie.
This function requires that a ResponseWriter is present in the context.
type CrockfordGenerator ¶
type CrockfordGenerator struct {
Length int
}
CrockfordGenerator generates random tokens using Douglas Crockford's base 32 alphabet which limits characters of similar appearances. The Sanitize method of this generator will deal with transcribing incorrect characters back to the correct value.
func NewCrockfordGenerator ¶
func NewCrockfordGenerator(l int) *CrockfordGenerator
NewCrockfordGenerator returns a new Crockford token generator that creates tokens of the specified length.
type Email ¶
type Email struct { Body []struct { // contains filtered or unexported fields } To string Subject string Date time.Time }
Email is a helper for creating multipart (text and html) emails
func (*Email) AddBody ¶
AddBody adds a content section to the email. The `contentType` should be a known type, such as "text/html" or "text/plain". If no `contentType` is provided, "text/plain" is used. Call this method for each required body, with the most preferable type last.
type LogTransport ¶
LogTransport is intended for testing/debugging purposes that simply logs the token to the console.
type MemStore ¶
type MemStore struct {
// contains filtered or unexported fields
}
MemStore is a Store that keeps tokens in memory, expiring them periodically when they expire.
func (*MemStore) Release ¶
func (s *MemStore) Release()
Release disposes of the MemStore and any released resources
type PINGenerator ¶
type PINGenerator struct {
Length int
}
PINGenerator generates numerical PINs of the specifeid length.
type Passwordless ¶
type Passwordless struct { Strategies map[string]Strategy Store TokenStore }
Passwordless holds a set of named strategies and an associated token store.
func New ¶
func New(store TokenStore) *Passwordless
New returns a new Passwordless instance with the specified token store. Register strategies against this instance with either `SetStrategy` or `SetTransport`.
func (*Passwordless) GetStrategy ¶
GetStrategy returns the Strategy of the given name, or nil if one does not exist.
func (*Passwordless) ListStrategies ¶
func (p *Passwordless) ListStrategies(ctx context.Context) map[string]Strategy
ListStrategies returns a list of strategies valid for the context mapped to their names. If you have multiple strategies, call this in order to provide a list of options for the user to pick from.
func (*Passwordless) RequestToken ¶
func (p *Passwordless) RequestToken(ctx context.Context, s, uid, recipient string) error
RequestToken generates and delivers a token to the given user. If the specified strategy is not known or not valid, an error is returned.
func (*Passwordless) SetStrategy ¶
func (p *Passwordless) SetStrategy(name string, s Strategy)
SetStrategy registers the given strategy.
func (*Passwordless) SetTransport ¶
func (p *Passwordless) SetTransport(name string, t Transport, g TokenGenerator, ttl time.Duration) Strategy
SetTransport registers a transport strategy under a specified name. The TTL specifies for how long tokens generated with the provided TokenGenerator are valid. Some delivery mechanisms may require longer TTLs than others depending on the nature/punctuality of the transport.
func (*Passwordless) VerifyToken ¶
VerifyToken verifies the provided token is valid.
type RedisStore ¶
type RedisStore struct {
// contains filtered or unexported fields
}
RedisStore is a Store that keeps tokens in Redis.
func NewRedisStore ¶
func NewRedisStore(client redis.UniversalClient) *RedisStore
NewRedisStore creates and returns a new `RedisStore`.
func (RedisStore) Delete ¶
func (s RedisStore) Delete(ctx context.Context, uid string) error
Delete removes a key from the store.
type SMTPTransport ¶
type SMTPTransport struct { UseSSL bool // contains filtered or unexported fields }
SMTPTransport delivers a user token via e-mail.
func NewSMTPTransport ¶
func NewSMTPTransport(addr, from string, auth smtp.Auth, c ComposerFunc) *SMTPTransport
NewSMTPTransport returns a new transport capable of sending emails via SMTP. `addr` should be in the form "host:port" of the email server.
type SimpleStrategy ¶
type SimpleStrategy struct { Transport TokenGenerator // contains filtered or unexported fields }
SimpleStrategy is a convenience wrapper combining a Transport, TokenGenerator, and TTL.
type Strategy ¶
type Strategy interface { Transport TokenGenerator // TTL should return the time-to-live of generated tokens. TTL(context.Context) time.Duration // Valid should return true if this strategy is valid for the provided // context. Valid(context.Context) bool }
Strategy defines how to send and what tokens to send to users.
type TokenGenerator ¶
type TokenGenerator interface { // Generate should return a token and nil error on success, or an empty // string and error on failure. Generate(ctx context.Context) (string, error) // Sanitize should take a user provided input and sanitize it such that // it can be passed to a function that expects the same input as // `Generate()`. Useful for cases where the token may be subject to // minor transcription errors by a user. (e.g. 0 == O) Sanitize(ctx context.Context, s string) (string, error) }
TokenGenerator defines an interface for generating and sanitising cryptographically-secure tokens.
type TokenStore ¶
type TokenStore interface { // Store securely stores the given token with the given expiry time Store(ctx context.Context, token, uid string, ttl time.Duration) error // Exists returns true if a token is stored for the user. If the expiry // time is available this is also returned, otherwise it will be zero // and can be tested with `Time.IsZero()`. Exists(ctx context.Context, uid string) (bool, time.Time, error) // Verify returns true if the given token is valid for the user Verify(ctx context.Context, token, uid string) (bool, error) // Delete removes the token for the specified user Delete(ctx context.Context, uid string) error }
TokenStore is a storage mechanism for tokens.
type Transport ¶
type Transport interface { // Send instructs the transport to send the given token for the specified // user to the given recipient, which could be an email address, phone // number, or something else. Send(ctx context.Context, token, user, recipient string) error }
Transport represents a mechanism that sends a named recipient a token.