smolauth

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Oct 21, 2024 License: MIT Imports: 23 Imported by: 0

README

smolauth

A small and opinionated auth package for go. Designed to suit just my needs.

May suit your needs if you:

  • Use session-based authentication
  • Use net/http
  • Use sqlite or postgres for storing user and session data

Features

  • Login/Logout sessions
  • Password hashing
  • Auth middleware
  • OAuth providers
    • Google
    • Github
    • Facebook
Not-Features
  • Validation (password strength, password confirmation, email format, etc)
  • Email verification
  • Forgot password

SQL Schemas

If you need extended user or account schemas, I suggest using a one-to-one table relationship or optional columns should be fine.

Sqlite
CREATE TABLE users (
	id INTEGER PRIMARY KEY NOT NULL,
	email TEXT UNIQUE,
	password_hash TEXT,
	created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE accounts (
	id INTEGER PRIMARY KEY NOT NULL,
	user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE,
	created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
	provider TEXT NOT NULL,
	provider_id TEXT NOT NULL,
	access_token TEXT NOT NULL,
	refresh_token TEXT,
	access_token_expires_at DATETIME NOT NULL
);

CREATE UNIQUE INDEX accounts_provider_provider_id_idx ON accounts (provider, provider_id);

CREATE TABLE sessions (
	token TEXT PRIMARY KEY,
	data BLOB NOT NULL,
	expiry REAL NOT NULL
);

CREATE INDEX sessions_expiry_idx ON sessions(expiry);
Postgresql
CREATE TABLE users (
	id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
	email TEXT UNIQUE,
	password_hash TEXT,
	created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE accounts (
	id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
	user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE,
	created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
	provider TEXT NOT NULL,
	provider_id TEXT NOT NULL,
	access_token TEXT NOT NULL,
	refresh_token TEXT,
	access_token_expires_at TIMESTAMPTZ NOT NULL
);

CREATE UNIQUE INDEX accounts_provider_provider_id_idx ON accounts (provider, provider_id);

CREATE TABLE sessions (
	token TEXT PRIMARY KEY,
	data BYTEA NOT NULL,
	expiry TIMESTAMPTZ NOT NULL
);

CREATE INDEX sessions_expiry_idx ON sessions (expiry);

Documentation

Index

Constants

View Source
const OAUTH_STATE_SESSION_KEY = "oauth_state"
View Source
const OAUTH_VERIFIER_SESSION_KEY = "oauth_verifier"
View Source
const SessionUserIdKey = "user_id"

Variables

View Source
var (
	ErrUserExists      = errors.New("user already exists with that email")
	ErrInvalidEmail    = errors.New("invalid email")
	ErrInvalidPassword = errors.New("invalid password")
)
View Source
var ErrStateMismatch = errors.New("state mismatch")
View Source
var (
	ErrUnauthenticated = errors.New("unauthenticated")
)

Functions

func AuthLoadMiddleware

func AuthLoadMiddleware(manager *AuthManager) alice.Chain

func GenerateState

func GenerateState() (string, error)

GenerateState generates a random state string, base64 urlencoded with a length of 64 bytes

func RequireAuthMiddleware

func RequireAuthMiddleware(authManager *AuthManager) alice.Constructor

func ValidateState

func ValidateState(r *http.Request) error

ValidateState checks if the state in the query matches the state in the cookie, returns ErrStateMismatch if the states do not match Assumes the state cookie name is OAUTH_STATE_SESSION_KEY

Types

type AuthManager

type AuthManager struct {
	// SessionManager from https://github.com/alexedwards/scs, can be assigned to a custom session manager,
	// only guaranteed to work with pgxstore and sqlite3store
	SessionManager *scs.SessionManager

	// Optional logger for debugging
	Logger *slog.Logger
	// contains filtered or unexported fields
}

func NewAuthManager

func NewAuthManager(opts AuthOpts) *AuthManager

func (*AuthManager) CheckPassword

func (am *AuthManager) CheckPassword(email string, password string) (int, error)

CheckPassword checks if the password is correct for the given email Returns user id if the password is correct to be used in the session Returns ErrInvalidEmail if the user with email is not found Returns ErrInvalidPassword if the password doesn't match the hash

func (*AuthManager) GetUser

func (am *AuthManager) GetUser(r *http.Request) (ReadUser, error)

func (*AuthManager) GetUserCtx added in v0.2.0

func (am *AuthManager) GetUserCtx(ctx context.Context) (ReadUser, error)

func (*AuthManager) HandleOAuth

func (am *AuthManager) HandleOAuth(name string) http.HandlerFunc

func (*AuthManager) HandleOAuthCallback

func (am *AuthManager) HandleOAuthCallback(name string) http.HandlerFunc

func (*AuthManager) Login

func (am *AuthManager) Login(r *http.Request, data SessionData) error

func (*AuthManager) LoginCtx added in v0.2.0

func (am *AuthManager) LoginCtx(ctx context.Context, data SessionData) error

func (*AuthManager) Logout

func (am *AuthManager) Logout(r *http.Request) error

func (*AuthManager) LogoutCtx added in v0.2.0

func (am *AuthManager) LogoutCtx(ctx context.Context) error

func (*AuthManager) PasswordSignup

func (am *AuthManager) PasswordSignup(email string, password string) (int, error)

func (*AuthManager) ThirdPartySignup

func (am *AuthManager) ThirdPartySignup(user UserAccount) (int, error)

func (*AuthManager) WithGithub added in v0.2.0

func (am *AuthManager) WithGithub(provider *GithubProvider)

func (*AuthManager) WithGoogle

func (am *AuthManager) WithGoogle(provider *GoogleProvider)

func (*AuthManager) WithLogger added in v0.2.0

func (am *AuthManager) WithLogger(logger *slog.Logger)

func (*AuthManager) WithPostgres

func (am *AuthManager) WithPostgres(pool *pgxpool.Pool)

func (*AuthManager) WithProvider

func (am *AuthManager) WithProvider(name string, provider OAuthProvider)

Add generic OAuth provider not covered by the built-in providers

func (*AuthManager) WithSqlite

func (am *AuthManager) WithSqlite(db *sql.DB)

type AuthOpts

type AuthOpts struct {
	Cookie          scs.SessionCookie
	SessionDuration time.Duration
}

type GithubProvider added in v0.2.0

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

GitHub

func NewGithubProvider added in v0.2.0

func NewGithubProvider(clientId, clientSecret, redirectUrl string, postCallbackUrl string, extraScopes []string) *GithubProvider

func (*GithubProvider) HandleAuth added in v0.2.0

func (ghp *GithubProvider) HandleAuth(w http.ResponseWriter, r *http.Request)

func (*GithubProvider) HandleCallback added in v0.2.0

func (ghp *GithubProvider) HandleCallback(authManager *AuthManager) http.HandlerFunc

type GoogleProvider

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

Google

func NewGoogleProvider

func NewGoogleProvider(clientId, clientSecret, redirectUrl string, postCallbackUrl string, extraScopes []string) *GoogleProvider

func (*GoogleProvider) HandleAuth

func (gp *GoogleProvider) HandleAuth(w http.ResponseWriter, r *http.Request)

func (*GoogleProvider) HandleCallback

func (gp *GoogleProvider) HandleCallback(authManager *AuthManager) http.HandlerFunc

type GoogleToken

type GoogleToken struct {
	AccessToken  string    `json:"access_token"`
	RefreshToken string    `json:"refresh_token,omitempty"`
	Expiry       time.Time `json:"expiry,omitempty"`
	IDToken      string    `json:"id_token"`
	ExpiresIn    *int      `json:"expires_in,omitempty"`
	Scope        string    `json:"scope"`
}

GoogleToken is a custom struct to hold the oidc token response ExpiresIn remaining lifetime of the token in seconds

type OAuthProvider

type OAuthProvider interface {
	// HandleAuth should handle generating and redirecting to the OAuth provider's authorization URL
	HandleAuth(w http.ResponseWriter, r *http.Request)
	// HandleCallback should handle the callback from the OAuth provider, exchange the code for tokens, and updating the database
	HandleCallback(authManager *AuthManager) http.HandlerFunc
}

type ReadUser

type ReadUser struct {
	Id        int       `json:"id"`
	Email     string    `json:"email"`
	CreatedAt time.Time `json:"created_at" db:"created_at"`
}

type SessionData

type SessionData struct {
	// UserId is the primary key of the user in the database
	UserId int
}

type UserAccount

type UserAccount struct {
	Provider             string
	ProviderId           string
	AccessToken          string
	RefreshToken         string
	Email                string
	AccessTokenExpiresAt time.Time
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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