faroe

package module
v0.5.1 Latest Latest
Warning

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

Go to latest
Published: Sep 15, 2025 License: MIT Imports: 21 Imported by: 0

README

github.com/faroedev/faroe

Documentation at faroe.dev.

This software is in active development and has only gone through minimal testing.

Faroe is a modular auth server distributed as a Go package.

go get github.com/faroedev/faroe

Some key features of the server:

  1. Takes care of all the hard parts. Passwords, email address verification, sessions, rate limiting, password resets, and more.
  2. Extends your existing user database instead of replacing it. Own and customize your user data. No more data synchronization between servers.
  3. No direct connections to your database.
  4. Only ephemeral data is stored. Less things to manage and worry about.
const result = await client.createSignup(emailAddress);
if (!result.ok) {
    console.log(result.errorCode);
    return;
}
console.log(result.signup);
window.localStorage.setItem("signup_token", result.signupToken);

The package has no hard dependencies. All you need is a key-value store and an email server.

package main

import "github.com/faroedev/faroe"

func main() {
	server := faroe.NewServer(
		storage,
		userStore,
		logger,
		userPasswordHashAlgorithms,
		temporaryPasswordHashAlgorithm,
		cpuCount,
		faroe.RealClock,
		faroe.AllowAllEmailAddresses,
		emailSender,
		sessionConfig,
	)
}

Only password authentication is supported. Support for passkeys and 2FA are planned but there are no immediate plans to add social login (e.g. Sign in with Google).

Documentation

Index

Constants

View Source
const (
	ActionCreateSignup                             = "create_signup"
	ActionGetSignup                                = "get_signup"
	ActionDeleteSignup                             = "delete_signup"
	ActionSendSignupEmailAddressVerificationCode   = "send_signup_email_address_verification_code"
	ActionVerifySignupEmailAddressVerificationCode = "verify_signup_email_address_verification_code"
	ActionSetSignupPassword                        = "set_signup_password"
	ActionCompleteSignup                           = "complete_signup"

	ActionCreateSignin             = "create_signin"
	ActionGetSignin                = "get_signin"
	ActionDeleteSignin             = "delete_signin"
	ActionVerifySigninUserPassword = "verify_signin_user_password"
	ActionCompleteSignin           = "complete_signin"

	ActionGetSession        = "get_session"
	ActionDeleteSession     = "delete_session"
	ActionDeleteAllSessions = "delete_all_sessions"

	ActionCreateUserEmailAddressUpdate                             = "create_user_email_address_update"
	ActionGetUserEmailAddressUpdate                                = "get_user_email_address_update"
	ActionDeleteUserEmailAddressUpdate                             = "delete_user_email_address_update"
	ActionSendUserEmailAddressUpdateEmailAddressVerificationCode   = "send_user_email_address_update_email_address_verification_code"
	ActionVerifyUserEmailAddressUpdateEmailAddressVerificationCode = "verify_user_email_address_update_email_address_verification_code"
	ActionVerifyUserEmailAddressUpdateUserPassword                 = "verify_user_email_address_update_user_password"
	ActionCompleteUserEmailAddressUpdate                           = "complete_user_email_address_update"

	ActionCreateUserPasswordUpdate             = "create_user_password_update"
	ActionGetUserPasswordUpdate                = "get_user_password_update"
	ActionDeleteUserPasswordUpdate             = "delete_user_password_update"
	ActionVerifyUserPasswordUpdateUserPassword = "verify_user_password_update_user_password"
	ActionSetUserPasswordUpdateNewPassword     = "set_user_password_update_new_password"
	ActionCompleteUserPasswordUpdate           = "complete_user_password_update"

	ActionCreateUserDeletion             = "create_user_deletion"
	ActionGetUserDeletion                = "get_user_deletion"
	ActionDeleteUserDeletion             = "delete_user_deletion"
	ActionVerifyUserDeletionUserPassword = "verify_user_deletion_user_password"
	ActionCompleteUserDeletion           = "complete_user_deletion"

	ActionCreateUserPasswordReset                  = "create_user_password_reset"
	ActionGetUserPasswordReset                     = "get_user_password_reset"
	ActionDeleteUserPasswordReset                  = "delete_user_password_reset"
	ActionVerifyUserPasswordResetTemporaryPassword = "verify_user_password_reset_temporary_password"
	ActionSetUserPasswordResetNewPassword          = "set_user_password_reset_new_password"
	ActionCompleteUserPasswordReset                = "complete_user_password_reset"
)

Variables

View Source
var AllowAllEmailAddresses = allowAllEmailAddressesStruct{}

Implements EmailAddressCheckerInterface. Allows all email addresses.

View Source
var ErrStorageEntryAlreadyExists = errors.New("entry already exists in storage")
View Source
var ErrStorageEntryNotFound = errors.New("entry not found in storage")
View Source
var ErrUserStoreUserEmailAddressAlreadyUsed = errors.New("user email address already used")
View Source
var ErrUserStoreUserNotFound = errors.New("user not found")
View Source
var RealClock = realClockStruct{}

Implements ClockInterface. Uses the current system time.

Functions

This section is empty.

Types

type ActionErrorLoggerInterface

type ActionErrorLoggerInterface interface {
	LogActionError(timestamp time.Time, message string, actionInvocationId string, action string)
}

type ActionInvocationEndpointClientInterface

type ActionInvocationEndpointClientInterface interface {
	// Sends a request to an action invocation endpoint with the request body.
	// Returns the string body of a 200 response.
	// An error is returned if a 200 response could be received (after one or several attempts).
	SendActionInvocationEndpointRequest(body string) (string, error)
}

type ClockInterface

type ClockInterface interface {
	// Returns the current time.
	Now() time.Time
}

type EmailAddressCheckerInterface

type EmailAddressCheckerInterface interface {
	// Returns true, nil if the email address is allowed and false, nil if not.
	CheckEmailAddress(emailAddress string) (bool, error)
}

type EmailSenderInterface

type EmailSenderInterface interface {
	// Send an email to emailAddress.
	SendSignupEmailAddressVerificationCode(emailAddress string, emailAddressVerificationCode string) error

	// Send an email to emailAddress.
	// displayName may be an empty string.
	SendUserEmailAddressUpdateEmailVerificationCode(emailAddress string, displayName string, emailAddressVerificationCode string) error

	// Send an email to emailAddress.
	// displayName may be an empty string.
	SendUserPasswordResetTemporaryPassword(emailAddress string, displayName string, temporaryPassword string) error

	// Send an email to emailAddress.
	// displayName may be an empty string.
	SendUserSignedInNotification(emailAddress string, displayName string, timestamp time.Time) error

	// Send an email to emailAddress.
	// displayName may be an empty string.
	SendUserEmailAddressUpdatedNotification(emailAddress string, displayName string, newEmailAddress string, timestamp time.Time) error

	// Send an email to emailAddress.
	// displayName may be an empty string.
	SendUserPasswordUpdatedNotification(emailAddress string, displayName string, timestamp time.Time) error
}

type PasswordHashAlgorithmInterface

type PasswordHashAlgorithmInterface interface {
	// A unique identifier of this algorithm.
	Id() string

	SaltSize() int

	Hash(password string, salt []byte) ([]byte, error)
}

type ServerStruct

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

Use NewServer.

func NewServer

func NewServer(
	storage StorageInterface,
	userStore UserStoreInterface,
	errorLogger ActionErrorLoggerInterface,
	userPasswordHashAlgorithms []PasswordHashAlgorithmInterface,
	temporaryPasswordHashAlgorithm PasswordHashAlgorithmInterface,
	maxConcurrentPasswordHashingProcesses int,
	clock ClockInterface,
	newEmailAddressChecker EmailAddressCheckerInterface,
	emailSender EmailSenderInterface,
	sessionConfig SessionConfigStruct,
) *ServerStruct

All interfaces must be defined and cannot be nil.

maxConcurrentPasswordHashingProcesses defines the maximum number of concurrent processes for user password and temporary password hashing.

emailAddressChecker is used for checking email addresses for signup and new email addresses of user email address updates. It is not used in for sign ins or user password resets.

InactivityTimeout and ActivityCheckInterval should be a non-zero value in sessionConfig.

func (*ServerStruct) ResolveActionInvocationEndpointRequestWithAllowlist

func (server *ServerStruct) ResolveActionInvocationEndpointRequestWithAllowlist(bodyJSON string, allowlist []string) (string, error)

Resolves an action invocation endpoint request, only accepting actions in the allowlist. Returns the string body of a 200 response or an error if the request is invalid.

func (*ServerStruct) ResolveActionInvocationEndpointRequestWithBlocklist

func (server *ServerStruct) ResolveActionInvocationEndpointRequestWithBlocklist(bodyJSON string, blocklist []string) (string, error)

Resolves an action invocation endpoint request, accepting all actions except for ones in the blocklist. Returns the string body of a 200 response or an error if the request is invalid.

type SessionConfigStruct

type SessionConfigStruct struct {
	// A non-zero, positive value. Defines how long an inactive session remains valid.
	InactivityTimeout time.Duration

	// A non-zero, positive value. Defines how often session activity is recorded.
	ActivityCheckInterval time.Duration

	// Positive value. 0 if disabled
	Expiration time.Duration

	// Positive value. 0 if disabled
	UserCacheExpiration time.Duration
}

type StorageInterface added in v0.5.0

type StorageInterface interface {
	// Retrieves the value and counter associated with the given key
	// Returns [ErrStorageEntryNotFound] if the key doesn't exist.
	// An error is returned for any other failure.
	Get(key string) ([]byte, int32, error)

	// Stores the value under the given key.
	// The counter is set to 0.
	// expiresAt is just a soft expiration hint.
	// There is no requirement for the entry to be immediately deleted or considered invalid after expiration.
	// Returns [ErrStorageEntryAlreadyExists] if the key already exists.
	// An error is returned for any other failure.
	Add(key string, value []byte, expiresAt time.Time) error

	// Updates the value and TTL for the given key if the counter matches.
	// expiresAt is just a soft expiration hint.
	// There is no requirement for the entry to be immediately deleted or considered invalid after expiration.
	// Returns [ErrStorageEntryNotFound] if the key doesn't exist or the counter doesn't match.
	// An error is returned for any other failure.
	Update(key string, value []byte, expiresAt time.Time, counter int32) error

	// Removes the value associated with the given key.
	// Returns [ErrStorageEntryNotFound] if the key doesn't exist.
	// An error is returned for any other failure.
	Delete(key string) error
}

Keys have a maximum length of 255 bytes.

type UserServerClientStruct added in v0.4.0

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

Use NewUserServerClient. Implements UserStoreInterface.

func NewUserServerClient added in v0.4.0

func NewUserServerClient(actionInvocationEndpointClient ActionInvocationEndpointClientInterface) *UserServerClientStruct

func (*UserServerClientStruct) CreateUser added in v0.4.0

func (userServerClient *UserServerClientStruct) CreateUser(emailAddress string, passwordHash []byte, passwordHashAlgorithmId string, passwordSalt []byte) (UserStruct, error)

func (*UserServerClientStruct) DeleteUser added in v0.4.0

func (userServerClient *UserServerClientStruct) DeleteUser(userId string) error

func (*UserServerClientStruct) GetUser added in v0.4.0

func (userServerClient *UserServerClientStruct) GetUser(userId string) (UserStruct, error)

func (*UserServerClientStruct) GetUserByEmailAddress added in v0.4.0

func (userServerClient *UserServerClientStruct) GetUserByEmailAddress(emailAddress string) (UserStruct, error)

func (*UserServerClientStruct) IncrementUserSessionsCounter added in v0.4.0

func (userServerClient *UserServerClientStruct) IncrementUserSessionsCounter(userId string, userSessionsCounter int32) error

func (*UserServerClientStruct) UpdateUserEmailAddress added in v0.4.0

func (userServerClient *UserServerClientStruct) UpdateUserEmailAddress(userId string, emailAddress string, userEmailAddressCounter int32) error

func (*UserServerClientStruct) UpdateUserPasswordHash added in v0.4.0

func (userServerClient *UserServerClientStruct) UpdateUserPasswordHash(userId string, passwordHash []byte, passwordHashAlgorithmId string, passwordSalt []byte, userPasswordHashCounter int32) error

type UserStoreInterface added in v0.4.0

type UserStoreInterface interface {
	// Creates a new user. The email address should be stored as-is, with the casing preserved.
	// The email address counter, password hash counter, disabled counter, and sessions counter should be set to 0.
	// Returns the created user if successful.
	// Returns [ErrUserStoreUserEmailAddressAlreadyUsed] if the email address is already tied to another user.
	// An error is returned for any other failure.
	CreateUser(emailAddress string, passwordHash []byte, passwordHashAlgorithmId string, passwordSalt []byte) (UserStruct, error)

	// Gets a user.
	// Returns [ErrUserStoreUserNotFound] if a user doesn't exist.
	// An error is returned for any other failure.
	GetUser(userId string) (UserStruct, error)

	// Gets a user by email address.
	// The email address must match exactly, including letter casing.
	// Returns [ErrUserStoreUserNotFound] if a user doesn't exist.
	// An error is returned for any other failure.
	GetUserByEmailAddress(emailAddress string) (UserStruct, error)

	// Updates a user's email address and increment a user's email address counter if the email address counter matches.
	// The new email address should be stored as-is, with the casing preserved.
	// Returns [ErrUserStoreUserNotFound] if a user doesn't exist or the user's email address counter doesn't match.
	// Returns [ErrUserStoreUserEmailAddressAlreadyUsed] if the email address is already tied to another user.
	// An error is returned for any other failure.
	UpdateUserEmailAddress(userId string, emailAddress string, userEmailAddressCounter int32) error

	// Updates a user's password hash, password hash algorithm ID, and password salt,
	// as well as increment a user's email address counter, if the user password hash counter matches.
	// Returns [ErrUserStoreUserNotFound] if a user doesn't exist or the user's password hash counter doesn't match.
	// An error is returned for any other failure.
	UpdateUserPasswordHash(userId string, passwordHash []byte, passwordHashAlgorithmId string, passwordSalt []byte, userPasswordHashCounter int32) error

	// Increments a user's sessions counter if the user's sessions counter matches.
	// Returns [ErrUserStoreUserNotFound] if a user doesn't exist or the user's sessions counter doesn't match.
	// An error is returned for any other failure.
	IncrementUserSessionsCounter(userId string, userSessionsCounter int32) error

	// Deletes a user.
	// Returns [ErrUserStoreUserNotFound] if a user doesn't exist.
	// An error is returned for any other failure.
	DeleteUser(userId string) error
}

type UserStruct added in v0.4.0

type UserStruct struct {
	// A unique ID.
	Id string

	// A unique email address.
	EmailAddress string

	PasswordHash []byte

	PasswordSalt []byte

	PasswordHashAlgorithmId string

	Disabled bool

	// An empty string if not defined.
	DisplayName string

	EmailAddressCounter int32

	PasswordHashCounter int32

	DisabledCounter int32

	SessionsCounter int32
}

Jump to

Keyboard shortcuts

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