Main logic
parent
3b6af9165d
commit
236ed765e2
|
@ -0,0 +1,3 @@
|
|||
LISTEN=:80
|
||||
POSTGRES=postgresql://root@pg:5432/core?sslmode=disable
|
||||
REDIS=redis:6379
|
|
@ -0,0 +1,15 @@
|
|||
FROM golang:1.12-rc-alpine as build
|
||||
|
||||
RUN apk add --no-cache git=2.20.1-r0
|
||||
|
||||
WORKDIR /src
|
||||
COPY go.mod go.sum .env *.go ./
|
||||
RUN go get -d -v ./...
|
||||
RUN CGO_ENABLED=0 go build -ldflags "-s -w"
|
||||
|
||||
FROM scratch
|
||||
|
||||
COPY --from=build /src/permissions /permissions
|
||||
COPY --from=build /src/.env /.env
|
||||
|
||||
ENTRYPOINT ["/permissions"]
|
36
README.md
36
README.md
|
@ -1,3 +1,39 @@
|
|||
# beep-permissions
|
||||
|
||||
Beep backend handling user permissions. Currently, permissions are defined as user-scope (i.e. userid in conversationid). If no such pairing exists, permission is denied. Might consider moving to searchms style user-scope-action system later.
|
||||
|
||||
Relations are cached in redis to avoid excessive querying time. A listener updates the cache on database changes.
|
||||
|
||||
## Environment variables
|
||||
|
||||
Supply environment variables by either exporting them or editing `.env`.
|
||||
|
||||
| ENV | Description | Default |
|
||||
| --- | ----------- | ------- |
|
||||
| LISTEN | Host and port for service to listen on | :80 |
|
||||
| POSTGRES | URL of postgres | postgresql://root@pg:5432/core?sslmode=disable |
|
||||
| REDIS | URL of redis | redis:6379 |
|
||||
|
||||
## API
|
||||
|
||||
| Contents |
|
||||
| -------- |
|
||||
| Get Permission |
|
||||
|
||||
---
|
||||
|
||||
### Get Permission
|
||||
|
||||
```
|
||||
GET /user/:userid/conversation/:conversationid
|
||||
```
|
||||
|
||||
Query to see if userid-conversationid is permissable.
|
||||
|
||||
#### Params
|
||||
|
||||
#### Success (200 OK)
|
||||
|
||||
Empty body.
|
||||
|
||||
#### Errors
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
module permissions
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/go-redis/redis v6.15.2+incompatible
|
||||
github.com/joho/godotenv v1.3.0
|
||||
github.com/julienschmidt/httprouter v1.2.0
|
||||
github.com/lib/pq v1.1.1
|
||||
)
|
|
@ -0,0 +1,8 @@
|
|||
github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4=
|
||||
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
|
||||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
96
main.go
96
main.go
|
@ -5,14 +5,19 @@ import (
|
|||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/lib/pq"
|
||||
"github.com/go-redis/redis"
|
||||
)
|
||||
|
||||
var listen string
|
||||
var postgres string
|
||||
var redisHost string
|
||||
|
||||
var redisClient *redis.Client
|
||||
|
||||
func main() {
|
||||
// Load .env
|
||||
|
@ -22,6 +27,14 @@ func main() {
|
|||
}
|
||||
listen = os.Getenv("LISTEN")
|
||||
postgres = os.Getenv("POSTGRES")
|
||||
redisHost = os.Getenv("REDIS")
|
||||
|
||||
// Redis
|
||||
redisClient = redis.NewClient(&redis.Options{
|
||||
Addr: redisHost,
|
||||
Password: "",
|
||||
DB: 2,
|
||||
})
|
||||
|
||||
// Postgres
|
||||
log.Printf("connecting to postgres %s", postgres)
|
||||
|
@ -29,12 +42,91 @@ func main() {
|
|||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Populate cache
|
||||
rows, err := db.Query(`
|
||||
SELECT user, conversation FROM "member"
|
||||
`)
|
||||
if err != nil {
|
||||
log.Fatal("Error retrieving records from database")
|
||||
}
|
||||
for rows.Next() {
|
||||
var userID, conversationID string
|
||||
if err := rows.Scan(&userID, &conversationID); err != nil {
|
||||
log.Fatal("Error retrieving records from database")
|
||||
}
|
||||
id := userID + "+" + conversationID
|
||||
redisClient.Set(id, true, 0)
|
||||
}
|
||||
rows.Close()
|
||||
db.Close()
|
||||
|
||||
// Start cache update listener
|
||||
minReconn := 10 * time.Second
|
||||
maxReconn := 1 * time.Minute
|
||||
listener := pq.NewListener(postgres, minReconn, maxReconn, func(ev pq.ListenerEventType, err error) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
} else if ev == pq.ListenerEventConnected {
|
||||
log.Println("listener connected")
|
||||
}
|
||||
})
|
||||
|
||||
// INSERT/UPDATE Listener
|
||||
err = listener.Listen("member_new")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// DELETE Listener
|
||||
err = listener.Listen("member_delete")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Process events
|
||||
go ListenForEvents(listener)
|
||||
|
||||
// Routes
|
||||
router := httprouter.New()
|
||||
router.GET("/user/:userid/conversation/:conversationid", GetPermission)
|
||||
|
||||
// Serve
|
||||
log.Printf("starting server on %s", listen)
|
||||
log.Fatal(http.ListenAndServe(listen, router))
|
||||
}
|
||||
|
||||
func ListenForEvents(listener *pq.Listener) {
|
||||
for {
|
||||
select {
|
||||
case n := <-listener.Notify:
|
||||
if n.Channel == "member_new" {
|
||||
redisClient.Set(n.Extra, true, 0)
|
||||
} else if n.Channel == "member_delete" {
|
||||
redisClient.Del(n.Extra)
|
||||
}
|
||||
case <- time.After(90 * time.Second):
|
||||
go func() {
|
||||
listener.Ping()
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetPermission(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
userID := p.ByName("userid")
|
||||
conversationID := p.ByName("conversationid")
|
||||
|
||||
id := userID + "+" + conversationID
|
||||
|
||||
exists, err := redisClient.Exists(id).Result()
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
} else if exists == 0 {
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(200)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue