• Stars
    star
    513
  • Rank 86,178 (Top 2 %)
  • Language
    Go
  • License
    MIT License
  • Created almost 10 years ago
  • Updated 2 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

๐Ÿ” Middleware for keeping track of users, login states and permissions

Permissions2 Build GoDoc Go Report Card

Middleware for keeping track of users, login states and permissions.

Online API Documentation

godoc.org

Features and limitations

  • Uses secure cookies and stores user information in a Redis database.
  • Suitable for running a local Redis server, registering/confirming users and managing public/user/admin pages.
  • Also supports connecting to remote Redis servers.
  • Does not support SQL databases. For MariaDB/MySQL support, look into permissionsql.
  • For Bolt database support (no database host needed, uses a file), look into permissionbolt.
  • For PostgreSQL database support (using the HSTORE feature), look into pstore.
  • Supports registration and confirmation via generated confirmation codes.
  • Tries to keep things simple.
  • Only supports public, user and admin permissions out of the box, but offers functionality for implementing more fine grained permissions, if so desired.
  • The default permissions can be cleared with the Clear() function.
  • Supports Chi, Negroni, Martini, Gin, Goji and plain net/http.
  • Should also work with other frameworks, since the standard http.HandlerFunc is used everywhere.

Requirements

  • Redis >= 2.6.12
  • Go >= 1.17

Examples

There is more information after the examples.

Example for Chi

package main

import (
    "fmt"
    "log"
    "net/http"
    "strings"

    "github.com/go-chi/chi/v5"
    "github.com/xyproto/permissions2/v2"
)

func main() {
    m := chi.NewRouter()

    // New permissions middleware
    perm, err := permissions.New2()
    if err != nil {
        log.Fatalln(err)
    }

    // Blank slate, no default permissions
    //perm.Clear()

    // Get the userstate, used in the handlers below
    userstate := perm.UserState()

    // Set up the middleware handler for Chi
    m.Use(perm.Middleware)

    m.Get("/", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "Has user bob: %v\n", userstate.HasUser("bob"))
        fmt.Fprintf(w, "Logged in on server: %v\n", userstate.IsLoggedIn("bob"))
        fmt.Fprintf(w, "Is confirmed: %v\n", userstate.IsConfirmed("bob"))
        fmt.Fprintf(w, "Username stored in cookies (or blank): %v\n", userstate.Username(req))
        fmt.Fprintf(w, "Current user is logged in, has a valid cookie and *user rights*: %v\n", userstate.UserRights(req))
        fmt.Fprintf(w, "Current user is logged in, has a valid cookie and *admin rights*: %v\n", userstate.AdminRights(req))
        fmt.Fprintf(w, "\nTry: /register, /confirm, /remove, /login, /logout, /makeadmin, /clear, /data and /admin")
    })

    m.Get("/register", func(w http.ResponseWriter, r *http.Request) {
        userstate.AddUser("bob", "hunter1", "[email protected]")
        fmt.Fprintf(w, "User bob was created: %v\n", userstate.HasUser("bob"))
    })

    m.Get("/confirm", func(w http.ResponseWriter, r *http.Request) {
        userstate.MarkConfirmed("bob")
        fmt.Fprintf(w, "User bob was confirmed: %v\n", userstate.IsConfirmed("bob"))
    })

    m.Get("/remove", func(w http.ResponseWriter, r *http.Request) {
        userstate.RemoveUser("bob")
        fmt.Fprintf(w, "User bob was removed: %v\n", !userstate.HasUser("bob"))
    })

    m.Get("/login", func(w http.ResponseWriter, r *http.Request) {
        userstate.Login(w, "bob")
        fmt.Fprintf(w, "bob is now logged in: %v\n", userstate.IsLoggedIn("bob"))
    })

    m.Get("/logout", func(w http.ResponseWriter, r *http.Request) {
        userstate.Logout("bob")
        fmt.Fprintf(w, "bob is now logged out: %v\n", !userstate.IsLoggedIn("bob"))
    })

    m.Get("/makeadmin", func(w http.ResponseWriter, r *http.Request) {
        userstate.SetAdminStatus("bob")
        fmt.Fprintf(w, "bob is now administrator: %v\n", userstate.IsAdmin("bob"))
    })

    m.Get("/clear", func(w http.ResponseWriter, r *http.Request) {
        userstate.ClearCookie(w)
        fmt.Fprintf(w, "Clearing cookie")
    })

    m.Get("/data", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "user page that only logged in users must see!")
    })

    m.Get("/admin", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "super secret information that only logged in administrators must see!\n\n")
        if usernames, err := userstate.AllUsernames(); err == nil {
            fmt.Fprintf(w, "list of all users: "+strings.Join(usernames, ", "))
        }
    })

    // Custom handler for when permissions are denied
    perm.SetDenyFunction(func(w http.ResponseWriter, req *http.Request) {
        http.Error(w, "Permission denied!", http.StatusForbidden)
    })

    // Serve
    http.ListenAndServe(":3000", m)
}

Example for Negroni

package main

import (
    "fmt"
    "net/http"
    "strings"
    "log"

    "github.com/urfave/negroni"
    "github.com/xyproto/permissions2/v2"
)

func main() {
    n := negroni.Classic()
    mux := http.NewServeMux()

    // New permissions middleware
    perm, err := permissions.New2()
    if err != nil {
        log.Fatalln(err)
    }

    // Blank slate, no default permissions
    //perm.Clear()

    // Get the userstate, used in the handlers below
    userstate := perm.UserState()

    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "Has user bob: %v\n", userstate.HasUser("bob"))
        fmt.Fprintf(w, "Logged in on server: %v\n", userstate.IsLoggedIn("bob"))
        fmt.Fprintf(w, "Is confirmed: %v\n", userstate.IsConfirmed("bob"))
        fmt.Fprintf(w, "Username stored in cookies (or blank): %v\n", userstate.Username(req))
        fmt.Fprintf(w, "Current user is logged in, has a valid cookie and *user rights*: %v\n", userstate.UserRights(req))
        fmt.Fprintf(w, "Current user is logged in, has a valid cookie and *admin rights*: %v\n", userstate.AdminRights(req))
        fmt.Fprintf(w, "\nTry: /register, /confirm, /remove, /login, /logout, /makeadmin, /clear, /data and /admin")
    })

    mux.HandleFunc("/register", func(w http.ResponseWriter, req *http.Request) {
        userstate.AddUser("bob", "hunter1", "[email protected]")
        fmt.Fprintf(w, "User bob was created: %v\n", userstate.HasUser("bob"))
    })

    mux.HandleFunc("/confirm", func(w http.ResponseWriter, req *http.Request) {
        userstate.MarkConfirmed("bob")
        fmt.Fprintf(w, "User bob was confirmed: %v\n", userstate.IsConfirmed("bob"))
    })

    mux.HandleFunc("/remove", func(w http.ResponseWriter, req *http.Request) {
        userstate.RemoveUser("bob")
        fmt.Fprintf(w, "User bob was removed: %v\n", !userstate.HasUser("bob"))
    })

    mux.HandleFunc("/login", func(w http.ResponseWriter, req *http.Request) {
        userstate.Login(w, "bob")
        fmt.Fprintf(w, "bob is now logged in: %v\n", userstate.IsLoggedIn("bob"))
    })

    mux.HandleFunc("/logout", func(w http.ResponseWriter, req *http.Request) {
        userstate.Logout("bob")
        fmt.Fprintf(w, "bob is now logged out: %v\n", !userstate.IsLoggedIn("bob"))
    })

    mux.HandleFunc("/makeadmin", func(w http.ResponseWriter, req *http.Request) {
        userstate.SetAdminStatus("bob")
        fmt.Fprintf(w, "bob is now administrator: %v\n", userstate.IsAdmin("bob"))
    })

    mux.HandleFunc("/clear", func(w http.ResponseWriter, req *http.Request) {
        userstate.ClearCookie(w)
        fmt.Fprintf(w, "Clearing cookie")
    })

    mux.HandleFunc("/data", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "user page that only logged in users must see!")
    })

    mux.HandleFunc("/admin", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "super secret information that only logged in administrators must see!\n\n")
        if usernames, err := userstate.AllUsernames(); err == nil {
            fmt.Fprintf(w, "list of all users: "+strings.Join(usernames, ", "))
        }
    })

    // Custom handler for when permissions are denied
    perm.SetDenyFunction(func(w http.ResponseWriter, req *http.Request) {
        http.Error(w, "Permission denied!", http.StatusForbidden)
    })

    // Enable the permissions middleware
    n.Use(perm)

    // Use mux for routing, this goes last
    n.UseHandler(mux)

    // Serve
    n.Run(":3000")
}

Example for Martini

package main

import (
    "fmt"
    "net/http"
    "strings"
    "log"

    "github.com/go-martini/martini"
    "github.com/xyproto/permissions2/v2"
)

func main() {
    m := martini.Classic()

    // New permissions middleware
    perm, err := permissions.New2()
    if err != nil {
        log.Fatalln(err)
    }

    // Blank slate, no default permissions
    //perm.Clear()

    // Get the userstate, used in the handlers below
    userstate := perm.UserState()

    m.Get("/", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "Has user bob: %v\n", userstate.HasUser("bob"))
        fmt.Fprintf(w, "Logged in on server: %v\n", userstate.IsLoggedIn("bob"))
        fmt.Fprintf(w, "Is confirmed: %v\n", userstate.IsConfirmed("bob"))
        fmt.Fprintf(w, "Username stored in cookies (or blank): %v\n", userstate.Username(req))
        fmt.Fprintf(w, "Current user is logged in, has a valid cookie and *user rights*: %v\n", userstate.UserRights(req))
        fmt.Fprintf(w, "Current user is logged in, has a valid cookie and *admin rights*: %v\n", userstate.AdminRights(req))
        fmt.Fprintf(w, "\nTry: /register, /confirm, /remove, /login, /logout, /makeadmin, /clear, /data and /admin")
    })

    m.Get("/register", func(w http.ResponseWriter) {
        userstate.AddUser("bob", "hunter1", "[email protected]")
        fmt.Fprintf(w, "User bob was created: %v\n", userstate.HasUser("bob"))
    })

    m.Get("/confirm", func(w http.ResponseWriter) {
        userstate.MarkConfirmed("bob")
        fmt.Fprintf(w, "User bob was confirmed: %v\n", userstate.IsConfirmed("bob"))
    })

    m.Get("/remove", func(w http.ResponseWriter) {
        userstate.RemoveUser("bob")
        fmt.Fprintf(w, "User bob was removed: %v\n", !userstate.HasUser("bob"))
    })

    m.Get("/login", func(w http.ResponseWriter) {
        userstate.Login(w, "bob")
        fmt.Fprintf(w, "bob is now logged in: %v\n", userstate.IsLoggedIn("bob"))
    })

    m.Get("/logout", func(w http.ResponseWriter) {
        userstate.Logout("bob")
        fmt.Fprintf(w, "bob is now logged out: %v\n", !userstate.IsLoggedIn("bob"))
    })

    m.Get("/makeadmin", func(w http.ResponseWriter) {
        userstate.SetAdminStatus("bob")
        fmt.Fprintf(w, "bob is now administrator: %v\n", userstate.IsAdmin("bob"))
    })

    m.Get("/clear", func(w http.ResponseWriter) {
        userstate.ClearCookie(w)
        fmt.Fprintf(w, "Clearing cookie")
    })

    m.Get("/data", func(w http.ResponseWriter) {
        fmt.Fprintf(w, "user page that only logged in users must see!")
    })

    m.Get("/admin", func(w http.ResponseWriter) {
        fmt.Fprintf(w, "super secret information that only logged in administrators must see!\n\n")
        if usernames, err := userstate.AllUsernames(); err == nil {
            fmt.Fprintf(w, "list of all users: "+strings.Join(usernames, ", "))
        }
    })

    // Set up a middleware handler for Martini, with a custom "permission denied" message.
    permissionHandler := func(w http.ResponseWriter, req *http.Request, c martini.Context) {
        // Check if the user has the right admin/user rights
        if perm.Rejected(w, req) {
            // Deny the request
            http.Error(w, "Permission denied!", http.StatusForbidden)
            // Reject the request by not calling the next handler below
            return
        }
        // Call the next middleware handler
        c.Next()
    }

    // Enable the permissions middleware
    m.Use(permissionHandler)

    // Serve
    m.Run()
}

Example for Gin

package main

import (
    "fmt"
    "net/http"
    "strings"
    "log"

    "github.com/gin-gonic/gin"
    "github.com/xyproto/permissions2/v2"
)

func main() {
    g := gin.New()

    // New permissions middleware
    perm, err := permissions.New2()
    if err != nil {
        log.Fatalln(err)
    }

    // Blank slate, no default permissions
    //perm.Clear()

    // Set up a middleware handler for Gin, with a custom "permission denied" message.
    permissionHandler := func(c *gin.Context) {
        // Check if the user has the right admin/user rights
        if perm.Rejected(c.Writer, c.Request) {
            // Deny the request, don't call other middleware handlers
            c.AbortWithStatus(http.StatusForbidden)
            fmt.Fprint(c.Writer, "Permission denied!")
            return
        }
        // Call the next middleware handler
        c.Next()
    }

    // Logging middleware
    g.Use(gin.Logger())

    // Enable the permissions middleware, must come before recovery
    g.Use(permissionHandler)

    // Recovery middleware
    g.Use(gin.Recovery())

    // Get the userstate, used in the handlers below
    userstate := perm.UserState()

    g.GET("/", func(c *gin.Context) {
        msg := ""
        msg += fmt.Sprintf("Has user bob: %v\n", userstate.HasUser("bob"))
        msg += fmt.Sprintf("Logged in on server: %v\n", userstate.IsLoggedIn("bob"))
        msg += fmt.Sprintf("Is confirmed: %v\n", userstate.IsConfirmed("bob"))
        msg += fmt.Sprintf("Username stored in cookies (or blank): %v\n", userstate.Username(c.Request))
        msg += fmt.Sprintf("Current user is logged in, has a valid cookie and *user rights*: %v\n", userstate.UserRights(c.Request))
        msg += fmt.Sprintf("Current user is logged in, has a valid cookie and *admin rights*: %v\n", userstate.AdminRights(c.Request))
        msg += fmt.Sprintln("\nTry: /register, /confirm, /remove, /login, /logout, /makeadmin, /clear, /data and /admin")
        c.String(http.StatusOK, msg)
    })

    g.GET("/register", func(c *gin.Context) {
        userstate.AddUser("bob", "hunter1", "[email protected]")
        c.String(http.StatusOK, fmt.Sprintf("User bob was created: %v\n", userstate.HasUser("bob")))
    })

    g.GET("/confirm", func(c *gin.Context) {
        userstate.MarkConfirmed("bob")
        c.String(http.StatusOK, fmt.Sprintf("User bob was confirmed: %v\n", userstate.IsConfirmed("bob")))
    })

    g.GET("/remove", func(c *gin.Context) {
        userstate.RemoveUser("bob")
        c.String(http.StatusOK, fmt.Sprintf("User bob was removed: %v\n", !userstate.HasUser("bob")))
    })

    g.GET("/login", func(c *gin.Context) {
        // Headers will be written, for storing a cookie
        userstate.Login(c.Writer, "bob")
        c.String(http.StatusOK, fmt.Sprintf("bob is now logged in: %v\n", userstate.IsLoggedIn("bob")))
    })

    g.GET("/logout", func(c *gin.Context) {
        userstate.Logout("bob")
        c.String(http.StatusOK, fmt.Sprintf("bob is now logged out: %v\n", !userstate.IsLoggedIn("bob")))
    })

    g.GET("/makeadmin", func(c *gin.Context) {
        userstate.SetAdminStatus("bob")
        c.String(http.StatusOK, fmt.Sprintf("bob is now administrator: %v\n", userstate.IsAdmin("bob")))
    })

    g.GET("/clear", func(c *gin.Context) {
        userstate.ClearCookie(c.Writer)
        c.String(http.StatusOK, "Clearing cookie")
    })

    g.GET("/data", func(c *gin.Context) {
        c.String(http.StatusOK, "user page that only logged in users must see!")
    })

    g.GET("/admin", func(c *gin.Context) {
        c.String(http.StatusOK, "super secret information that only logged in administrators must see!\n\n")
        if usernames, err := userstate.AllUsernames(); err == nil {
            c.String(http.StatusOK, "list of all users: "+strings.Join(usernames, ", "))
        }
    })

    // Serve
    g.Run(":3000")
}

Example for Goji

package main

import (
    "fmt"
    "net/http"
    "strings"
    "log"

    "github.com/xyproto/permissions2/v2"
    "github.com/zenazn/goji"
)

func main() {
    // New permissions middleware
    perm, err := permissions.New2()
    if err != nil {
        log.Fatalln(err)
    }

    // Blank slate, no default permissions
    //perm.Clear()

    // Get the userstate, used in the handlers below
    userstate := perm.UserState()

    goji.Get("/", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "Has user bob: %v\n", userstate.HasUser("bob"))
        fmt.Fprintf(w, "Logged in on server: %v\n", userstate.IsLoggedIn("bob"))
        fmt.Fprintf(w, "Is confirmed: %v\n", userstate.IsConfirmed("bob"))
        fmt.Fprintf(w, "Username stored in cookies (or blank): %v\n", userstate.Username(req))
        fmt.Fprintf(w, "Current user is logged in, has a valid cookie and *user rights*: %v\n", userstate.UserRights(req))
        fmt.Fprintf(w, "Current user is logged in, has a valid cookie and *admin rights*: %v\n", userstate.AdminRights(req))
        fmt.Fprintf(w, "\nTry: /register, /confirm, /remove, /login, /logout, /makeadmin, /clear, /data and /admin")
    })

    goji.Get("/register", func(w http.ResponseWriter, req *http.Request) {
        userstate.AddUser("bob", "hunter1", "[email protected]")
        fmt.Fprintf(w, "User bob was created: %v\n", userstate.HasUser("bob"))
    })

    goji.Get("/confirm", func(w http.ResponseWriter, req *http.Request) {
        userstate.MarkConfirmed("bob")
        fmt.Fprintf(w, "User bob was confirmed: %v\n", userstate.IsConfirmed("bob"))
    })

    goji.Get("/remove", func(w http.ResponseWriter, req *http.Request) {
        userstate.RemoveUser("bob")
        fmt.Fprintf(w, "User bob was removed: %v\n", !userstate.HasUser("bob"))
    })

    goji.Get("/login", func(w http.ResponseWriter, req *http.Request) {
        userstate.Login(w, "bob")
        fmt.Fprintf(w, "bob is now logged in: %v\n", userstate.IsLoggedIn("bob"))
    })

    goji.Get("/logout", func(w http.ResponseWriter, req *http.Request) {
        userstate.Logout("bob")
        fmt.Fprintf(w, "bob is now logged out: %v\n", !userstate.IsLoggedIn("bob"))
    })

    goji.Get("/makeadmin", func(w http.ResponseWriter, req *http.Request) {
        userstate.SetAdminStatus("bob")
        fmt.Fprintf(w, "bob is now administrator: %v\n", userstate.IsAdmin("bob"))
    })

    goji.Get("/clear", func(w http.ResponseWriter, req *http.Request) {
        userstate.ClearCookie(w)
        fmt.Fprintf(w, "Clearing cookie")
    })

    goji.Get("/data", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "user page that only logged in users must see!")
    })

    goji.Get("/admin", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "super secret information that only logged in administrators must see!\n\n")
        if usernames, err := userstate.AllUsernames(); err == nil {
            fmt.Fprintf(w, "list of all users: "+strings.Join(usernames, ", "))
        }
    })

    // Custom "permissions denied" message
    perm.SetDenyFunction(func(w http.ResponseWriter, req *http.Request) {
        http.Error(w, "Permission denied!", http.StatusForbidden)
    })

    // Permissions middleware for Goji
    permissionHandler := func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
            // Check if the user has the right admin/user rights
            if perm.Rejected(w, req) {
                // Deny the request
                perm.DenyFunction()(w, req)
                return
            }
            // Serve the requested page
            next.ServeHTTP(w, req)
        })
    }

    // Enable the permissions middleware
    goji.Use(permissionHandler)

    // Goji will listen to port 8000 by default
    goji.Serve()
}

Example for just net/http

package main

import (
    "fmt"
    "log"
    "net/http"
    "strings"
    "time"

    "github.com/xyproto/permissions2/v2"
    "github.com/xyproto/pinterface"
)

type permissionHandler struct {
    // perm is a Permissions structure that can be used to deny requests
    // and acquire the UserState. By using `pinterface.IPermissions` instead
    // of `*permissions.Permissions`, the code is compatible with not only
    // `permissions2`, but also other modules that uses other database
    // backends, like `permissionbolt` which uses Bolt.
    perm pinterface.IPermissions

    // The HTTP multiplexer
    mux *http.ServeMux
}

// Implement the ServeHTTP method to make a permissionHandler a http.Handler
func (ph *permissionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    // Check if the user has the right admin/user rights
    if ph.perm.Rejected(w, req) {
        // Let the user know, by calling the custom "permission denied" function
        ph.perm.DenyFunction()(w, req)
        // Reject the request
        return
    }
    // Serve the requested page if permissions were granted
    ph.mux.ServeHTTP(w, req)
}

func main() {
    mux := http.NewServeMux()

    // New permissions middleware
    perm, err := permissions.New2()
    if err != nil {
        log.Fatalln(err)
    }

    // Blank slate, no default permissions
    //perm.Clear()

    // Get the userstate, used in the handlers below
    userstate := perm.UserState()

    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "Has user bob: %v\n", userstate.HasUser("bob"))
        fmt.Fprintf(w, "Logged in on server: %v\n", userstate.IsLoggedIn("bob"))
        fmt.Fprintf(w, "Is confirmed: %v\n", userstate.IsConfirmed("bob"))
        fmt.Fprintf(w, "Username stored in cookies (or blank): %v\n", userstate.Username(req))
        fmt.Fprintf(w, "Current user is logged in, has a valid cookie and *user rights*: %v\n", userstate.UserRights(req))
        fmt.Fprintf(w, "Current user is logged in, has a valid cookie and *admin rights*: %v\n", userstate.AdminRights(req))
        fmt.Fprintf(w, "\nTry: /register, /confirm, /remove, /login, /logout, /makeadmin, /clear, /data and /admin")
    })

    mux.HandleFunc("/register", func(w http.ResponseWriter, req *http.Request) {
        userstate.AddUser("bob", "hunter1", "[email protected]")
        fmt.Fprintf(w, "User bob was created: %v\n", userstate.HasUser("bob"))
    })

    mux.HandleFunc("/confirm", func(w http.ResponseWriter, req *http.Request) {
        userstate.MarkConfirmed("bob")
        fmt.Fprintf(w, "User bob was confirmed: %v\n", userstate.IsConfirmed("bob"))
    })

    mux.HandleFunc("/remove", func(w http.ResponseWriter, req *http.Request) {
        userstate.RemoveUser("bob")
        fmt.Fprintf(w, "User bob was removed: %v\n", !userstate.HasUser("bob"))
    })

    mux.HandleFunc("/login", func(w http.ResponseWriter, req *http.Request) {
        userstate.Login(w, "bob")
        fmt.Fprintf(w, "bob is now logged in: %v\n", userstate.IsLoggedIn("bob"))
    })

    mux.HandleFunc("/logout", func(w http.ResponseWriter, req *http.Request) {
        userstate.Logout("bob")
        fmt.Fprintf(w, "bob is now logged out: %v\n", !userstate.IsLoggedIn("bob"))
    })

    mux.HandleFunc("/makeadmin", func(w http.ResponseWriter, req *http.Request) {
        userstate.SetAdminStatus("bob")
        fmt.Fprintf(w, "bob is now administrator: %v\n", userstate.IsAdmin("bob"))
    })

    mux.HandleFunc("/clear", func(w http.ResponseWriter, req *http.Request) {
        userstate.ClearCookie(w)
        fmt.Fprintf(w, "Clearing cookie")
    })

    mux.HandleFunc("/data", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "user page that only logged in users must see!")
    })

    mux.HandleFunc("/admin", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "super secret information that only logged in administrators must see!\n\n")
        if usernames, err := userstate.AllUsernames(); err == nil {
            fmt.Fprintf(w, "list of all users: "+strings.Join(usernames, ", "))
        }
    })

    // Custom handler for when permissions are denied
    perm.SetDenyFunction(func(w http.ResponseWriter, req *http.Request) {
        http.Error(w, "Permission denied!", http.StatusForbidden)
    })

    // Configure the HTTP server and permissionHandler struct
    s := &http.Server{
        Addr:           ":3000",
        Handler:        &permissionHandler{perm, mux},
        ReadTimeout:    10 * time.Second,
        WriteTimeout:   10 * time.Second,
        MaxHeaderBytes: 1 << 20,
    }

    log.Println("Listening for requests on port 3000")

    // Start listening
    log.Fatal(s.ListenAndServe())
}

Default permissions

  • Visiting the /admin path prefix requires the user to be logged in with admin rights, by default.
  • These path prefixes requires the user to be logged in, by default: /repo and /data
  • These path prefixes are public by default: /, /login, /register, /style, /img, /js, /favicon.ico, /robots.txt and /sitemap_index.xml

The default permissions can be cleared with the Clear() function.

Password hashing

  • bcrypt is used by default for hashing passwords. sha256 is also supported.
  • By default, all new passwords will be hashed with bcrypt.
  • For backwards compatibility, old password hashes with the length of a sha256 hash will be checked with sha256. To disable this behavior, and only ever use bcrypt, add this line: userstate.SetPasswordAlgo("bcrypt")

Coding style

  • The code shall always be formatted with go fmt.

Setting and getting properties for users

  • Setting a property:
username := "bob"
propertyName := "clever"
propertyValue := "yes"

userstate.Users().Set(username, propertyName, propertyValue)
  • Getting a property:
username := "bob"
propertyName := "clever"
propertyValue, err := userstate.Users().Get(username, propertyName)
if err != nil {
    log.Print(err)
    return err
}
fmt.Printf("%s is %s: %s\n", username, propertyName, propertyValue)

This method can also be used for deleting users, by for example setting a deleted property to true.

Passing userstate between functions, files and to other Go packages

Using the pinterface.IUserState interface (from the pinterface package) makes it possible to pass UserState structs between functions, also in other packages. By using this, it is possible to seamlessly change the database backend from, for instance, Redis (permissions2) to BoltDB (permissionbolt).

pstore, permissionsql, permissionbolt and permissions2 are interchangeable.

Retrieving the underlying Redis database

Here is a short example application for retrieving the underlying Redis pool and connection:

package main

import (
    "fmt"
    "github.com/gomodule/redigo/redis"
    "github.com/xyproto/permissions2/v2"
)

func main() {
    perm, err := permissions.New2()
    if err != nil {
        fmt.Println("Could not open Redis database")
        return
    }
    ustate := perm.UserState()

    // A bit of checking is needed, since the database backend is interchangeable
    pustate, ok := ustate.(*permissions.UserState)
    if !ok {
        fmt.Println("Not using the Redis database backend")
        return
    }

    // Convert from a simpleredis.ConnectionPool to a redis.Pool
    redisPool := redis.Pool(*pustate.Pool())
    fmt.Printf("Redis pool: %v (%T)\n", redisPool, redisPool)

    // Get the Redis connection as well
    redisConnection := redisPool.Get()
    fmt.Printf("Redis connection: %v (%T)\n", redisConnection, redisConnection)
}

Note that the redigo repository was recently moved to https://github.com/gomodule/redigo. The above code will not work if you use the old redigo package.

General information

More Repositories

1

algernon

Small self-contained pure-Go web server with Lua, Teal, Markdown, Ollama, HTTP/2, QUIC, Redis, SQLite and PostgreSQL support ++
Go
2,797
star
2

wallutils

๐ŸŒ† Utilities for handling monitors, resolutions, wallpapers and timed wallpapers
Go
354
star
3

sdl2-examples

SDL2 examples, for 25 different programming languages
Makefile
279
star
4

orbiton

๐ŸŒ€ Config-free text editor and IDE limited to VT100. Suitable for writing git commit messages, editing Markdown, config files, source code, viewing man pages and for quick edit-compile cycles when programming. Incl. syntax highlighting, jump-to-error, rainbow parentheses, macros, tab compl., cut/paste portals, a gdb front-end & ChatGPT supp.
Go
279
star
5

png2svg

๐Ÿ”€ Convert small PNG images to SVG Tiny 1.2
Go
228
star
6

gendesk

๐ŸŒฟ Generate .desktop files and download .png icons by specifying a minimum of information
Go
115
star
7

cxx

๐Ÿ”Œ Configuration-free utility for building, testing and packaging executables written in C++. Can auto-detect compilation flags based on includes, via the package system and pkg-config.
Python
95
star
8

permissionbolt

๐Ÿ”ฉ Middleware for keeping track of users, login states and permissions
Go
85
star
9

go2cpp

Go to C++20 transpiler
Go
85
star
10

battlestar

๐Ÿ’ซ A different take on Assembly, with the goal of creating tiny executables.
Go
73
star
11

simplebolt

๐Ÿ”ฉ Simple way to use the Bolt database
Go
65
star
12

fstabfmt

๐Ÿ”ต Format /etc/fstab automatically.
Go
59
star
13

permissionsql

๐Ÿ” Middleware for keeping track of users, login states and permissions
Go
59
star
14

c2go

๐Ÿš  The original c2go program. Attempts to convert C to Go. Works on several simple examples, but not complex applications.
C
56
star
15

simplehstore

๐Ÿช Easy way to use a PostgreSQL database (and the HSTORE feature) from Go
Go
54
star
16

mooseware

๐Ÿ’€ Skeleton for writing a middleware handler
Go
48
star
17

http2check

โœ… Utility for checking if a web server supports HTTP/2
Go
46
star
18

onthefly

๐Ÿ”— Generate HTML and CSS together, on the fly
Go
43
star
19

setconf

๐Ÿ“— Change settings in configuration text files
Python
41
star
20

pstore

๐Ÿ”ง Middleware for keeping track of users, login states and permissions, using the HSTORE feature in PostgreSQL
Go
33
star
21

simpleredis

๐Ÿ“ป Easy way to use Redis from Go
Go
25
star
22

wann

โš–๏ธ Weight Agnostic Neural Networks in Go
Go
25
star
23

cdetect

๐Ÿ”ฌ Detect which compiler and compiler version a Linux executable (in the ELF format) was compiled with
Go
24
star
24

vt100

๐Ÿ’ป VT100 Terminal Package
Go
24
star
25

splash

๐ŸŒŠ Syntax highlight code embedded in HTML with a splash of color. Also includes the auto-updated Chroma style gallery.
Go
23
star
26

elfinfo

Utility for displaying which compiler was used for creating an ELF file + basic info
HTML
22
star
27

botsay

๐Ÿค– Output text together with randomly generated ASCII robots in colors inspired by synthwave.
Go
22
star
28

ufw-extras

Extra ufw-* files for /etc/ufw/applications.d
19
star
29

tinyxxd

Drop-in replacement and standalone version of the hex dump utility that comes with ViM
C
18
star
30

distrodetector

๐Ÿ“‡ Detect which Linux distro, macOS or BSD version a system is running.
Go
18
star
31

jit

๐Ÿ–– Go module for executing machine code directly and a machine code interpreter.
Go
18
star
32

ainur

๐ŸŒŒ Detect compiler names and versions from ELF files
Go
16
star
33

echoperm

๐Ÿ“ฃ Middleware for echo for handling users, permissions and cookies
Go
16
star
34

metatar

Manipulate tar file metadata, list tar files or convert tar to cpio. For some projects, this can replace fakeroot and cpio, when creating an initrd image that is compatible with the Linux kernel.
Go
16
star
35

monkeyjump

๐Ÿ’ Minimalistic GUI for playing Go with GnuGo
Python
16
star
36

in

๐Ÿ“‚ Create a directory if needed, then run the given command there
Go
12
star
37

kal

๐Ÿ“‡ Utility with red days and flag flying days + calendar package for Go
Go
12
star
38

teaftp

๐Ÿต Simple, read-only TFTP server
Go
12
star
39

tinyionice

Drop-in replacement for ionice in 300 lines of C
C
11
star
40

gnetlark

๐Ÿฆ Fast HTTP server that supports handlers written in Starlark
Go
11
star
41

palgen

Create a palette of N colors or convert True Color images to indexed ones. Includes png2gpl and png2act.
Go
10
star
42

grimrec

๐Ÿ˜ฌ Record a window to a GIF, under Sway/Wayland
Python
10
star
43

term

๐Ÿ“บ Simple Terminal Interface
Go
9
star
44

pastefile

๐Ÿ“ƒ Create a file that contains the contents of the clipboard
Go
9
star
45

sealion

๐ŸŒŠ Prompt lunch reminder
Python
9
star
46

cupholder

๐Ÿต Remote CD tray ejection
Go
8
star
47

mcbanner

๐ŸŽ Application for generating Minecraft banners
Go
8
star
48

aget

Minimalistic AUR helper
Go
8
star
49

interfaces

๐Ÿง‰ List all network interfaces
Go
8
star
50

guessica

๐Ÿฅข Update a PKGBUILD file by guessing the latest version number and finding the latest git tag and hash online
Go
8
star
51

yaloco

๐Ÿธ Yet Another Log Colorizer
Go
7
star
52

simplemaria

๐Ÿก Easy way to use a MariaDB/MySQL database from Go
Go
7
star
53

pf

Apply functions to each pixel in an image, concurrently
Go
7
star
54

simpletimed

The Simple Timed Wallpaper specification + Go module
7
star
55

minitree

List files in columns
Python
7
star
56

pixelpusher

๐Ÿ‘พ Plot pixels concurrently on a nostalgic 320x200 256 color canvas
Go
7
star
57

randomstring

Generate random strings
Go
7
star
58

archlinux-wallpaper

Wallpapers for the archlinux-wallpaper package
7
star
59

plates

Package for dealing with RGB, HSV and HSL colors, mixing colors and for reading and writing images
Go
7
star
60

textoutput

๐Ÿ…ฐ๏ธ Output text, with and without colors
Go
6
star
61

datablock

๐ŸŒฟ Types and functions for caching files and directory listings with a fixed buffer size
Go
6
star
62

purefunction

Given a Go source code file, find all known pure functions
Go
6
star
63

sheepcounter

๐Ÿ‘ ResponseWriter that can count bytes written to the client
Go
6
star
64

sys

๐ŸŽฑ Wrapper for "systemctl" and "service" that never believes that "start" or "stop" is the name of a service
Shell
6
star
65

env

Provide default values when fetching environment variables
Go
6
star
66

alienpdf

๐Ÿ“ƒ Generate letters
Go
5
star
67

pinterface

๐Ÿ”ญ Interfaces for the permission* and simple* packages
Go
5
star
68

swish

โœ… Optimized Swish activation function, for neural networks
Go
5
star
69

cookie

๐Ÿช Functions related to cookies
Go
5
star
70

xpm

Encode images in the X PixMap (XPM3) image format
Go
5
star
71

kitchencalendar

๐Ÿ“† Generate per-week calendars that are meant to be printed out and hung up in a kitchen area
Go
5
star
72

scoreserver

โšพ REST/JSON server for managing users and scores
Go
5
star
73

symbolhash

Given a string, returns a unicode hash of the desired length
Go
5
star
74

dialog

๐ŸŒ Basic wrapper for the dialog executable
Go
5
star
75

emojiterm

List and display GitHub emojis directly on the terminal
Go
5
star
76

getver

๐ŸŽ Given an URL, get the current version for a project
Go
4
star
77

easy

๐Ÿง nice and ionice combined to a single utility using purely Go (no C)
Go
4
star
78

rangetype

๐Ÿ”ข Mini-language/DSL for defining and dealing with ranges of numbers
Go
4
star
79

carveimg

Two image viewing utilities for the terminal
Go
4
star
80

ask

๐Ÿ‘„ Ask the user a question
Go
4
star
81

tinysvg

๐Ÿ“ Package for generating TinySVG images
Go
4
star
82

archlog

๐Ÿ“’ Generates a ChangeLog from "svn log"
Go
4
star
83

tiddlywiki-launcher

Small script for launching a TiddlyWiki per user
Python
4
star
84

event2

Simple time-based event system, for triggering events at HH:MM
Go
4
star
85

termtitle

Change the title of the currently running terminal emulator
Go
4
star
86

addinclude

๐Ÿ”ผ Add include statements within the guards of a header file
Go
4
star
87

msg2

โ›ฒ Output a blue arrow followed by a bold message
C++
4
star
88

icostring

๐Ÿ‘๏ธ Generate a favicon.ico from a short string
Go
4
star
89

shrinky-intro

Skeleton for a 4k (demoscene) intro for 64-bit Linux
C++
4
star
90

burnfont

Hand-crafted 6x6 pixel font, defined by code
Go
4
star
91

binary

๐Ÿพ Detect if a file is binary or text
Go
4
star
92

plsclient

A client for gopls
Go
3
star
93

pamcan

Learn to type "pacman" correctly
Go
3
star
94

jumpline.vim

A solid keybinding for ctrl + l for ViM and NeoVim
Vim Script
3
star
95

smileypyramid

๐Ÿ”บ Example application for argument handling using docopt, for Rust and C++
C++
3
star
96

siteengines

Building blocks for creating a web page
Go
3
star
97

copy

Copy a file locally or over ssh, and ask before overwriting
Go
3
star
98

spheremover

๐ŸŸข Interactive real-time raytracing on the CPU, using OpenMP, SDL2 and C++
C++
3
star
99

pixelprotocol

Experimental protocol for streaming games
3
star
100

mime

Takes a file extension, returns a mime type
Go
3
star