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-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.
|
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"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
_ "github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
|
"github.com/go-redis/redis"
|
||||||
)
|
)
|
||||||
|
|
||||||
var listen string
|
var listen string
|
||||||
var postgres string
|
var postgres string
|
||||||
|
var redisHost string
|
||||||
|
|
||||||
|
var redisClient *redis.Client
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Load .env
|
// Load .env
|
||||||
|
@ -22,6 +27,14 @@ func main() {
|
||||||
}
|
}
|
||||||
listen = os.Getenv("LISTEN")
|
listen = os.Getenv("LISTEN")
|
||||||
postgres = os.Getenv("POSTGRES")
|
postgres = os.Getenv("POSTGRES")
|
||||||
|
redisHost = os.Getenv("REDIS")
|
||||||
|
|
||||||
|
// Redis
|
||||||
|
redisClient = redis.NewClient(&redis.Options{
|
||||||
|
Addr: redisHost,
|
||||||
|
Password: "",
|
||||||
|
DB: 2,
|
||||||
|
})
|
||||||
|
|
||||||
// Postgres
|
// Postgres
|
||||||
log.Printf("connecting to postgres %s", postgres)
|
log.Printf("connecting to postgres %s", postgres)
|
||||||
|
@ -29,12 +42,91 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
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
|
// Routes
|
||||||
router := httprouter.New()
|
router := httprouter.New()
|
||||||
|
router.GET("/user/:userid/conversation/:conversationid", GetPermission)
|
||||||
|
|
||||||
// Serve
|
// Serve
|
||||||
log.Printf("starting server on %s", listen)
|
log.Printf("starting server on %s", listen)
|
||||||
log.Fatal(http.ListenAndServe(listen, router))
|
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