Documentation ¶
Overview ¶
The goal of this project is to provide a web server module for Caddy that allows authenticated access to a website based on e-mail only. That is, no account or password should be necessary for access.
See README.md for general usage information.
Index ¶
- Constants
- Variables
- func GetBrowserContext(r *http.Request) string
- func GetCookie(r *http.Request) string
- func InitializeCrypto()
- type AuthByEmailHandler
- type Config
- type CookieToken
- type Crypto
- type Database
- type DiskBackedDatabase
- func (d *DiskBackedDatabase) AddUser(user UserID)
- func (d *DiskBackedDatabase) DelUser(user UserID) error
- func (d *DiskBackedDatabase) DeleteCookieToken(cookieToken string) error
- func (d *DiskBackedDatabase) GetCookieToken(cookieText string) *CookieToken
- func (d *DiskBackedDatabase) GetLinkToken(linkText string) *LinkToken
- func (d *DiskBackedDatabase) IsKnownUser(user UserID) bool
- func (d *DiskBackedDatabase) NewCookieToken(cookieToken CookieToken) (string, error)
- func (d *DiskBackedDatabase) NewLinkToken(linkToken LinkToken, validityPeriod time.Duration) (string, error)
- func (d *DiskBackedDatabase) ValidateCookieToken(cookieToken string) error
- type EmailAddr
- type EmailMessage
- type HtmlTemplate
- type LinkToken
- type LogMailer
- type Mailer
- type MailerInternal
- type MapBasedDatabase
- func (m *MapBasedDatabase) AddUser(user UserID)
- func (m *MapBasedDatabase) DelUser(user UserID) error
- func (m *MapBasedDatabase) DeleteCookieToken(cookieText string) error
- func (m *MapBasedDatabase) GetCookieToken(cookieText string) *CookieToken
- func (m *MapBasedDatabase) GetLinkToken(linkText string) *LinkToken
- func (m *MapBasedDatabase) IsKnownUser(user UserID) bool
- func (m *MapBasedDatabase) NewCookieToken(cookieToken CookieToken) (string, error)
- func (m *MapBasedDatabase) NewLinkToken(linkToken LinkToken, validityPeriod time.Duration) (string, error)
- func (m *MapBasedDatabase) ValidateCookieToken(cookieText string) error
- type RealMailer
- type SendInBlueMailer
- type TemplateID
- type UserID
Constants ¶
const MAILDATA_APPROVE = `` /* 469-byte string literal not displayed */
This is an e-mail sent to an administrator when a new user wants to log in. You can replace this page with your own by putting a file called `mail_login.html` in the `auth` subdirectory of your website root.
When supplying your own template, take care to include the fields {{.Admin}}, {{.User}}, {{.SiteName}} and {{.Link}} as shown below. Be mindful of the fact that many e-mail clients block external resources.
const MAILDATA_LOGIN = `` /* 317-byte string literal not displayed */
This is an e-mail sent to a user that wishes to log in. You can replace this page with your own by putting a file called `mail_login.html` in the `auth` subdirectory of your website root.
When supplying your own template, take care to include the fields {{.User}}, {{.SiteName}} and {{.Link}} as shown below. Be mindful of the fact that many e-mail clients block external resources.
const PAGEDATA_ACK_APPROVE = `` /* 180-byte string literal not displayed */
This page is shown to an administrator when they approve a user (by filling out the form in the `approve` template. You can replace this page with your own by putting a file called `ack_approve.html` in the `auth` subdirectory of your website root.
const PAGEDATA_ACK_LOGIN = `` /* 251-byte string literal not displayed */
This page is shown to any non-logged in user when they log in by entering their e-mail address, and are recognised as an existing user. You can replace this page with your own by putting a file called `ack_login.html` in the `auth` subdirectory of your website root.
const PAGEDATA_ACK_REMOVE = `` /* 146-byte string literal not displayed */
This page is shown to an administrator when they reject or delete a user (by filling out the form in the `approve` template. You can replace this page with your own by putting a file called `ack_remove.html` in the `auth` subdirectory of your website root.
const PAGEDATA_APPROVE = `` /* 1159-byte string literal not displayed */
This page is shown to a website administrator when they follow a link in an e-mail to approve or reject a new user. You can replace this page with your own by putting a file called `approve.html` in the `auth` subdirectory of your website root.
When supplying your own template, take care to include the fields {{.User}} and {{.EncEmail}} as shown below.
const PAGEDATA_DELETE = `` /* 347-byte string literal not displayed */
This page is shown to a user when they visit the /auth/delete endpoint with a GET request. It should ask them if they're sure.
const PAGEDATA_KIOSK = `` /* 831-byte string literal not displayed */
This page is shown to a user when they log in using a link that was created on another device than the one they're on. This may happen if they are e.g. in an internet kiosk, but receive mail on their phone.
The user is asked whether they want to log in the "remote" (kiosk) computer.
When supplying your own template, take care to include the fields {{.Browser}} and {{.Cookie}} as shown below.
const PAGEDATA_LOGIN = `` /* 411-byte string literal not displayed */
This page is shown to any non-logged in user when they try to access a protected resource. You can replace this page with your own by putting a file called `login.html` in the `auth` subdirectory of your website root.
Variables ¶
var Templates = map[TemplateID]HtmlTemplate{ TplLogin: { Filename: "auth/login.html", DefaultText: PAGEDATA_LOGIN, }, TplApprove: { Filename: "auth/approve.html", DefaultText: PAGEDATA_APPROVE, }, TplKiosk: { Filename: "auth/kiosk.html", DefaultText: PAGEDATA_KIOSK, }, TplDelete: { Filename: "auth/delete.html", DefaultText: PAGEDATA_DELETE, }, TplAckLogin: { Filename: "auth/ack_login.html", DefaultText: PAGEDATA_ACK_LOGIN, }, TplAckApprove: { Filename: "auth/ack_approve.html", DefaultText: PAGEDATA_ACK_APPROVE, }, TplAckRemove: { Filename: "auth/ack_remove.html", DefaultText: PAGEDATA_ACK_REMOVE, }, TplMailLogin: { Filename: "auth/mail_login.html", DefaultText: MAILDATA_LOGIN, }, TplMailApprove: { Filename: "auth/mail_approve.html", DefaultText: MAILDATA_APPROVE, }, }
This is a mapping from TemplateIDs to HTML templates used in this package.
Functions ¶
func GetBrowserContext ¶
GetBrowserContext gives a deterministic but human-readable representation of the browser that sent the request, like "Firefox/5.0 (Windows) at 12.13.14.1".
func InitializeCrypto ¶
func InitializeCrypto()
InitializeCrypto initializes the global CRYPTO struct with an hmac function and a block cipher. It uses the key in the environment variable AUTH_BY_EMAIL_KEY and panics if that variable is not present or not properly defined.
The keys for the cipher and the hmac function are each derived from the given key using a hmac function with a fixed key.
Types ¶
type AuthByEmailHandler ¶
type AuthByEmailHandler struct { Next httpserver.Handler // contains filtered or unexported fields }
func NewHandler ¶
func NewHandler(next httpserver.Handler, config *Config) AuthByEmailHandler
NewHandler initialises the package's various parts and returns the new Handler.
All errors cause a panic, since we can not run if some of the parts don't work. This may happen if it is impossible to initialize the cryptographic functions (for example because the cryptographic key is not present in the environment), if the database can not be initialised (for example because a location for the file was given, but can not be written to), or if the mailer can not be initialised (for example because the SendInBlue API key is not present in the environment).
func (AuthByEmailHandler) ServeHTTP ¶
func (h AuthByEmailHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error)
ServeHTTP serves a response in response to an HTTP request. The AuthByEmail handler will check whether the user is sufficiently authorized before passing the request on to the "next" handler, and if not, will instead serve an appropriate authorization interface.
The authentication module exposes a virtual directory /auth/, as in example.com/auth/. In it, the following endpoints exist:
auth/login - can be POSTed to with an email= field. This will initiate a Login check: if the email is approved, send a login email, if not, send email to an admin asking for access.
auth/wait - will wait after "login" in case the user approves the cookie elsewhere.
auth/logout - will log out a logged in user by removing their cookie from the database.
auth/welcome - can be GETted with a token, which if correct, sets a cookie, and forwards the request to the Next handler.
auth/approve - can be GETted with an e-mail, and will produce a form for an admin to decide whether to accept or refuse membership to that user. A POST request to the same endpoint executes that decision.
auth/delete - can be GETed, in which case it will ask for confirmation. A POST request to the same endpoint deletes the logged-in user from the database.
type Config ¶
type Config struct { Admins []*EmailAddr WhitelistDomains []string FilesystemRoot string Database string UnprotectedPaths []string Redirect string CookieValidity time.Duration SiteName string SiteURL string MailerFrom *EmailAddr }
The Config type contains parsed configuration information from the Caddyfile.
func NewConfigFromCaddy ¶
func NewConfigFromCaddy(c *caddy.Controller) (*Config, error)
NewConfigFromCaddy parses the caddyfile, and populates a new Config with values found there. It returns an error if these values are erroneous, or if mandatory parameters were not given.
func (*Config) IsDomainWhitelisted ¶
The helper function IsDomainWhitelisted checks whether the given domain is whitelisted by checking all members of WhitelistDomains
type CookieToken ¶
type CookieToken struct { // UserID corresponding to this token. UserID UserID // Whether this token has been validated (i.e. the user generating it can access the e-mail address belonging to UserID) IsValidated bool // Information about the browser to which this cookie was sent, for the user to identify the session later on. BrowserContext string }
A CookieToken contains all the information in a valid cookie
type Crypto ¶
type Crypto struct {
// contains filtered or unexported fields
}
var CRYPTO *Crypto
func (*Crypto) UserIDfromEmail ¶
type Database ¶
type Database interface { // GetCookieToken checks if the given string corresponds to a valid cookie // and returns the cookie information if so. GetCookieToken(cookieText string) *CookieToken // GetLinkToken checks if the given string corresponds to a sent email // and returns the result. If it does correspond to a valid user, the token is // returned GetLinkToken(linkText string) *LinkToken // IsKnownUser checks whether the UserID is valid IsKnownUser(user UserID) bool // NewCookieToken makes a fresh cookie token for the given user // and saves it to the database. NewCookieToken(cookieToken CookieToken) (string, error) // ValidateCookieToken sets the Validated property of this cookie to true. // If there is no such cookie, an error is returned. ValidateCookieToken(cookieText string) error // DeleteCookieToken removes a given cookie. If none exists, an error is returned. DeleteCookieToken(cookieText string) error // NewLinkToken makes a fresh link token for the given user // and saves it to the database NewLinkToken(linkToken LinkToken, validityPeriod time.Duration) (string, error) // AddUser adds the given user to the database AddUser(user UserID) // DelUser removes a user from the database DelUser(user UserID) error }
type DiskBackedDatabase ¶
type DiskBackedDatabase struct {
// contains filtered or unexported fields
}
A DiskBackedDatabase is an interface to an SQL database, which is disk-backed and therefore survives restarts of the webserver. This is the "canonical" database to use; the alternative (MapBasedDatabase) is used when no path to a database file is given and is intended for debugging or trial usage.
func NewDiskBackedDatabase ¶
func NewDiskBackedDatabase(config *Config, logger *log.Logger) *DiskBackedDatabase
NewDiskBackedDatabase opens or creates the database file, and sets up the database struct that interacts with it. If the database file does not exist, one is created. If creating the database file is not possible, this function panics.
func (*DiskBackedDatabase) AddUser ¶
func (d *DiskBackedDatabase) AddUser(user UserID)
AddUser adds the given user to the database
func (*DiskBackedDatabase) DelUser ¶
func (d *DiskBackedDatabase) DelUser(user UserID) error
DelUser removes a user from the database. Tokens corresponding to a non-existent user are invalid; if you re-add a user, tokens that were valid before deletion will become valid once more.
func (*DiskBackedDatabase) DeleteCookieToken ¶
func (d *DiskBackedDatabase) DeleteCookieToken(cookieToken string) error
DeleteCookieToken validates a cookie matching the given token
func (*DiskBackedDatabase) GetCookieToken ¶
func (d *DiskBackedDatabase) GetCookieToken(cookieText string) *CookieToken
GetCookieContents returns a given cookie if it exists and has not expired, nil otherwise.
func (*DiskBackedDatabase) GetLinkToken ¶
func (d *DiskBackedDatabase) GetLinkToken(linkText string) *LinkToken
GetLinkToken checks if the given string corresponds to a sent email and returns the result. If it does correspond to a valid user, that user's ID and the parsed link token are returned as well.
func (*DiskBackedDatabase) IsKnownUser ¶
func (d *DiskBackedDatabase) IsKnownUser(user UserID) bool
IsKnownUser checks whether the UserID is valid
func (*DiskBackedDatabase) NewCookieToken ¶
func (d *DiskBackedDatabase) NewCookieToken(cookieToken CookieToken) (string, error)
NewCookieToken makes a fresh cookie token for the given user
func (*DiskBackedDatabase) NewLinkToken ¶
func (d *DiskBackedDatabase) NewLinkToken(linkToken LinkToken, validityPeriod time.Duration) (string, error)
NewLinkToken makes a fresh link token for the given user
func (*DiskBackedDatabase) ValidateCookieToken ¶
func (d *DiskBackedDatabase) ValidateCookieToken(cookieToken string) error
ValidateCookieToken validates a cookie matching the given token
type EmailAddr ¶
func NewEmailAddrFromString ¶
NewEmailAddrFromString parses a string to find an e-mail address.
func (*EmailAddr) LocalPartIsASCII ¶
type EmailMessage ¶
EmailMessage represents a message sent by this mailer. There is no From address, since that is forced by SendInBlue to be the globally configured From address. Instead, we provide a setting for the Reply To address.
type HtmlTemplate ¶
HtmlTemplate is the pair of custom and default file contents for each template
type LinkToken ¶
type LinkToken struct { // UserID corresponding to this token. UserID UserID // Cookie token set on the browser from which this link was sent. CorrespondingCookie string }
A LinkToken contains all the information in a valid e-mail link
type LogMailer ¶
type LogMailer struct {
// contains filtered or unexported fields
}
The LogMailer is a dummy mailer that does not send mail. but instead prints messages to the log.
func (*LogMailer) DecryptEmail ¶
func (*LogMailer) SendAdminLoginRequest ¶
type Mailer ¶
type Mailer interface { // SendLoginLink sends a user an email with a login link using the given token SendLoginLink(email *EmailAddr, token string) error // SendAdminLoginRequest sends a user an email with an approval link for the given user SendAdminLoginRequest(email *EmailAddr) error // DecryptEmail decrypts an e-mail address that was given in an admin approval link DecryptEmail(encryptedEmail string) (*EmailAddr, error) }
type MailerInternal ¶
type MailerInternal interface { // Initialise the mailer (read config etc). Panics on error (e.g. no api-key in environment) Initialise(config *Config, logger *log.Logger) // Send a mail message SendMail(msg *EmailMessage) error }
type MapBasedDatabase ¶
type MapBasedDatabase struct {
// contains filtered or unexported fields
}
A database type for AuthByEmail that naively implements all functions of Database using maps. Not very fast or good, but useful as a first implementation, and as a guide for designing more mature databases.
See DiskBasedDatabase for function-level documentation.
func NewMapBasedDatabase ¶
func NewMapBasedDatabase() *MapBasedDatabase
func (*MapBasedDatabase) AddUser ¶
func (m *MapBasedDatabase) AddUser(user UserID)
AddUser adds the given user to the database
func (*MapBasedDatabase) DelUser ¶
func (m *MapBasedDatabase) DelUser(user UserID) error
DelUser removes a user from the database and invalidates all corresponding tokens
func (*MapBasedDatabase) DeleteCookieToken ¶
func (m *MapBasedDatabase) DeleteCookieToken(cookieText string) error
DeleteCookieToken validates a cookie matching the given token
func (*MapBasedDatabase) GetCookieToken ¶
func (m *MapBasedDatabase) GetCookieToken(cookieText string) *CookieToken
func (*MapBasedDatabase) GetLinkToken ¶
func (m *MapBasedDatabase) GetLinkToken(linkText string) *LinkToken
func (*MapBasedDatabase) IsKnownUser ¶
func (m *MapBasedDatabase) IsKnownUser(user UserID) bool
IsKnownUser checks whether the UserID is valid
func (*MapBasedDatabase) NewCookieToken ¶
func (m *MapBasedDatabase) NewCookieToken(cookieToken CookieToken) (string, error)
NewCookieToken makes a fresh cookie token for the given user and saves it to the database
func (*MapBasedDatabase) NewLinkToken ¶
func (m *MapBasedDatabase) NewLinkToken(linkToken LinkToken, validityPeriod time.Duration) (string, error)
NewLinkToken makes a fresh link token for the given user and saves it to the database
func (*MapBasedDatabase) ValidateCookieToken ¶
func (m *MapBasedDatabase) ValidateCookieToken(cookieText string) error
ValidateCookieToken validates a cookie matching the given token
type RealMailer ¶
type RealMailer struct {
// contains filtered or unexported fields
}
func NewRealMailer ¶
func NewRealMailer(config *Config, logger *log.Logger) *RealMailer
NewRealMailer returns a mailer with the given configuration. In this case, we use the SendInBlue implementation.
func (*RealMailer) DecryptEmail ¶
func (m *RealMailer) DecryptEmail(encryptedEmail string) (*EmailAddr, error)
DecryptEmail decrypts an e-mail address encrypted by encryptEmail. These are sent in the admin approval e-mails.
func (*RealMailer) SendAdminLoginRequest ¶
func (m *RealMailer) SendAdminLoginRequest(email *EmailAddr) error
SendAdminLoginRequest sends an approve/reject link for the given user to their admin.
func (*RealMailer) SendLoginLink ¶
func (m *RealMailer) SendLoginLink(email *EmailAddr, token string) error
SendLoginLink sends a login link with the given token to a user. The admin is given as the reply-to address.
type SendInBlueMailer ¶
type SendInBlueMailer struct {
// contains filtered or unexported fields
}
func (*SendInBlueMailer) Initialise ¶
func (m *SendInBlueMailer) Initialise(config *Config, logger *log.Logger)
NewSendInBlueMailer returns a new mailer with the given configuration. It reads an API key from the environment and will panic if it is not there, so make sure to set SENDINBLUE_API_KEY.
func (*SendInBlueMailer) SendMail ¶
func (m *SendInBlueMailer) SendMail(msg *EmailMessage) error
sendMail sends an e-mail message using the SendInBlue API. Normally we use the custom Send___ methods instead.
type TemplateID ¶
type TemplateID uint
const ( TplLogin TemplateID = iota TplApprove TplKiosk TplDelete TplAckLogin TplAckApprove TplAckRemove TplMailLogin TplMailApprove )
This is an enum listing the possible HTML templates used in this package. They are associated with a filename and default data in the Templates map.
Source Files ¶
- authByEmail.go
- checkAuthentication.go
- config.go
- crypto.go
- database.go
- diskBackedDatabase.go
- emailAddr.go
- handler.go
- logMailer.go
- mailer.go
- mailerInternal.go
- mapBasedDatabase.go
- realMailer.go
- sendInBlueMailer.go
- serialize.go
- serveApprove.go
- serveDelete.go
- serveErrors.go
- serveLogin.go
- serveLogout.go
- serveWait.go
- serveWelcome.go
- templates.go
- tokens.go
- userid.go