:closed_lock_with_key: Middleware for keeping track of users, login states and permissions

Permissions2 Build Status 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.8

Examples

There is more information after the examples.

Example for Chi

package main

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

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

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"
)

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"
)

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"
)

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"
    "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"
    "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 password 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"
)

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

Owner
Alexander F. Rødseth
Software Engineer, Arch Linux TU, Go/C/Python/C++ developer and open source enthusiast.
Alexander F. Rødseth
Comments
  • What is the right way to set deny function?

    What is the right way to set deny function?

    I am doing my admin module now and i can't seem to figure this part out. On the examples i see you check if perm.Rejected(w, req) but i don't seem to know how to set the rejected part.

    Lets say i just checked userstate.IsAdmin("bob") and it is false. How do i deny here and use middleware to check that.

    I am using echo but a good example should work too in any of those frameworks.

  • Great package ! Any word on JWT or Amazon S3 support?

    Great package ! Any word on JWT or Amazon S3 support?

    First of all, thank you. This is a great package indeed that works very well with Echo. I have tried a few but i am sticking with this one for now. I have a few questions concerning this package that could help me better implement it and clarify some things.

    • Why Redis database? It would only make sense if there was a way to make data persistent. I tried to look and see if that is the case but i was confused. Is the data persistent? I can testify that it is truly efficient and fast but i also want to save as much RAM/memory for my real program.

    • Bolt database stores data in the root directory in a file! To me that sounds a bit risky. Is that by design?

    • Also, bolt almost looks and works like Amazon S3. Would you mind supporting that in the future for folks like me that would rather save everything in a cloud key/value storage?

    • About JWT -- I can actually incorporate that but your package seems to almost implement things the same way. Am i losing anything by not using JWT vs cookies?

    Regards.

  • Change

    Change "username" to "useridentity" or "userid"

    Hello again,

    I have managed to incorporate permissions2 into my project and I think that small change can make a big difference. Permissions2 is by far the most flexible user auth package in go. By default "username" seems to be a unique field as I quess was the original intention of the project. But to make it really unique I have found myself using email or uuid for username. If it was "useridentity" or "userid", then I could easily use email or uuid or anything unique without having to worry about code readability later. I would also still have username available for an actual username or useralias.

  • Make cookie secret public variable

    Make cookie secret public variable

    We find out that every time when we restart server we have different cookie secret. We don't use rand package and don't seed it, that may be doing one of another package, but we don't have control over that.

  • Fix function comments based on best practices from Effective Go

    Fix function comments based on best practices from Effective Go

    Every exported function in a program should have a doc comment. The first sentence should be a summary that starts with the name being declared. From effective go.

    I generated this with CodeLingo and I'm keen to get some feedback, but this is automated so feel free to close it and just say "opt out" to opt out of future CodeLingo outreach PRs.

  • Use casbin as the permission backend

    Use casbin as the permission backend

    Hi, there is a project called casbin. It is an authorization library that supports models like MAC, RBAC, ABAC. Maybe it can assist your need in the perm management.

  • Database Migration.

    Database Migration.

    It has been great using this package for the last 4 or more years. I would like to migrate to a more robust user management system. Would you be able to help me understand the database setup? We can email privately if this is a security issue. Thanks.

  • Handling the errors from redis

    Handling the errors from redis

    Hi Folks,

    How can we handle the errors from Redis if we are using the default implementation?

    https://github.com/xyproto/permissions2/blob/74ad74d1250f34729ad77af31954bed040ca5d8d/userstate.go#L548-L549

    Thanks!

  • Encrypted connection to Redis

    Encrypted connection to Redis

    Hello

    I'm looking at connecting to Redis with the data encrypted in transit. One public example would be using AWS Elasticache as the Redis backend with the feature enabled as described here.

    It doesn't appear to be possible to configure this kind of encryption for permissions2. Is that correct or did I miss something? If yes, can you please consider adding support for this functionality? If not, can we have a code example?

    Speaking of which, the code examples are great and much appreciated!

    Thanks, Pierre

  • Please add support for mongodb

    Please add support for mongodb

    Expecting to get a version of permissions working with mongodb instead redis, this is really important feature, please implement it if possible. Thanks.

  • memory persistent or golang key/val alternative to redis?

    memory persistent or golang key/val alternative to redis?

    Hi, I'm interested in use permissions but I'd like avoid install redis for small projects, I know than redis is great and I'm currently using it in medium/big projects but for small ones seems a bit unnecessary, would be possible use maybe a key/value golang storage and avoid need install more tools?...or maybe a memory persistent...

    thanks

Bui api login - Bui api login in golang

bui-api-login Project setup go mod tidy -compat=1.17 .env.local OAUTH2_REDIRECT

Jan 7, 2022
simple-jwt-provider - Simple and lightweight provider which exhibits JWTs, supports login, password-reset (via mail) and user management.

Simple and lightweight JWT-Provider written in go (golang). It exhibits JWT for the in postgres persisted user, which can be managed via api. Also, a password-reset flow via mail verification is available. User specific custom-claims also available for jwt-generation and mail rendering.

Dec 18, 2022
an SSO and OAuth / OIDC login solution for Nginx using the auth_request module
an SSO and OAuth / OIDC login solution for Nginx using the auth_request module

Vouch Proxy An SSO solution for Nginx using the auth_request module. Vouch Proxy can protect all of your websites at once. Vouch Proxy supports many O

Jan 4, 2023
Goauth: Pre-made OAuth/OpenIDConnect and general authorization hooks for webapp login

goauth Pre-made OAuth/OpenIDConnect and general authorization hooks for webapp login. Currently supports Google, Facebook and Microsoft "out of the bo

Jan 28, 2022
Go login handlers for authentication providers (OAuth1, OAuth2)
Go login handlers for authentication providers (OAuth1, OAuth2)

gologin Package gologin provides chainable login http.Handler's for Google, Github, Twitter, Facebook, Bitbucket, Tumblr, or any OAuth1 or OAuth2 auth

Dec 30, 2022
JWT login microservice with plugable backends such as OAuth2, Google, Github, htpasswd, osiam, ..
JWT login microservice with plugable backends such as OAuth2, Google, Github, htpasswd, osiam, ..

loginsrv loginsrv is a standalone minimalistic login server providing a JWT login for multiple login backends. ** Attention: Update to v1.3.0 for Goog

Dec 24, 2022
Cache oci login token for kubectl

oci-token-cache Cache oci login token. This command cache oci login token into ~/.oci/token-cache.json and re-use for kubectl. Usage Currently, your ~

Nov 20, 2021
Lightweight SSO Login System

login Lightweight SSO Login System Convention Redirect to login.changkun.de?redirect=origin When login success, login.changkun.de will redirect to ori

Sep 29, 2022
JWT login microservice with plugable backends such as OAuth2, Google, Github, htpasswd

login-service login-service is a standalone minimalistic login server providing a (JWT)[https://jwt.io/] login for multiple login backends. Abstract l

Feb 12, 2022
Ginx - Evilginx2 - A man-in-the-middle attack framework used for phishing login credentials along with session cookies
Ginx - Evilginx2 - A man-in-the-middle attack framework used for phishing login credentials along with session cookies

evilginx2 is a man-in-the-middle attack framework used for phishing login creden

Mar 19, 2022
makes it easy to keep track of user sessions on a Go API.

usersession is a simple way to keep track of user information on a Go API. it assigns a session ID and gives you a place to store the IP and some user

Dec 22, 2021
Handle Web Authentication for Go apps that wish to implement a passwordless solution for users

WebAuthn Library This library is meant to handle Web Authentication for Go apps that wish to implement a passwordless solution for users. While the sp

Dec 30, 2022
Handle Web Authentication for Go apps that wish to implement a passwordless solution for users

WebAuthn Library This library is meant to handle Web Authentication for Go apps that wish to implement a passwordless solution for users. While the sp

Jan 1, 2023
JWT and Permission Middleware with MongoRPC

JWT and Permission Middleware with MongoRPC

Nov 19, 2021
This package provides json web token (jwt) middleware for goLang http servers

jwt-auth jwt auth middleware in goLang. If you're interested in using sessions, checkout my sessions library! README Contents: Quickstart Performance

Dec 5, 2022
A dead simple, highly performant, highly customizable sessions middleware for go http servers.

If you're interested in jwt's, see my jwt library! Sessions A dead simple, highly performant, highly customizable sessions service for go http servers

Dec 19, 2022
fastglue-csrf implements CSRF middleware for fastglue.

fastglue-csrf Overview fastglue-csrf implements CSRF middleware for fastglue.

Jan 5, 2022
ACL, RBAC, ABAC authorization middleware for KubeSphere

casbin-kubesphere-auth Casbin-kubesphere-auth is a plugin which apply several security authentication check on kubesphere via casbin. This plugin supp

Jun 9, 2022
OAuth 2.0 middleware service for chi (ported from gin by community member)

oauth middleware OAuth 2.0 Authorization Server & Authorization Middleware for go-chi This library was ported to go-chi from https://github.com/maxzer

Dec 8, 2022