Permissions2
:closed_lock_with_key: Middleware for keeping track of users, login states and permissions
Install / Use
/learn @xyproto/Permissions2README
Permissions2
Middleware for keeping track of users, login states and permissions.
Online API Documentation
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.HandlerFuncis used everywhere.
Requirements
- Redis >= 2.6.12 (or an open source Redis clone)
- Go >= 1.24
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", "bob@zombo.com")
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", "bob@zombo.com")
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
