5
0
Fork 0

Main logic

master
Daniel Lim 2019-06-23 04:07:28 +08:00
parent 3b6af9165d
commit 236ed765e2
6 changed files with 166 additions and 2 deletions

3
.env Normal file
View File

@ -0,0 +1,3 @@
LISTEN=:80
POSTGRES=postgresql://root@pg:5432/core?sslmode=disable
REDIS=redis:6379

15
Dockerfile Normal file
View File

@ -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"]

View File

@ -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

10
go.mod Normal file
View File

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

8
go.sum Normal file
View File

@ -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
View File

@ -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)
}