Skip to content

Commit

Permalink
feat: implement bouncer
Browse files Browse the repository at this point in the history
This service authorizes calls and converts extensions to users. This
information can be used in Asterisk to take action.
  • Loading branch information
crazybolillo committed Aug 4, 2024
1 parent 9fad17e commit 57793bb
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 7 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ docs:
swag init --generalInfo cmd/main.go --outputTypes=yaml

swagger:
docker run --detach -p 4000:8080 -e API_URL=/doc/swagger.yaml --mount 'type=bind,src=$(shell pwd)/docs,dst=/usr/share/nginx/html/doc' swaggerapi/swagger-ui
docker run --detach --name eryth-swagger -p 4000:8080 -e API_URL=/doc/swagger.yaml --mount 'type=bind,src=$(shell pwd)/docs,dst=/usr/share/nginx/html/doc' swaggerapi/swagger-ui
5 changes: 5 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"context"
"fmt"
"github.com/crazybolillo/eryth/internal/bouncer"
"github.com/crazybolillo/eryth/internal/handler"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
Expand Down Expand Up @@ -75,6 +76,10 @@ func serve(ctx context.Context) error {
endpoint := handler.Endpoint{Conn: conn}
r.Mount("/endpoint", endpoint.Router())

checker := &bouncer.Bouncer{Conn: conn}
authorization := handler.Authorization{Bouncer: checker}
r.Mount("/bouncer", authorization.Router())

slog.Info("Listening on :8080")
return http.ListenAndServe(":8080", r)
}
39 changes: 39 additions & 0 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
definitions:
bouncer.Response:
properties:
allow:
type: boolean
destination:
type: string
type: object
handler.AuthorizationRequest:
properties:
endpoint:
type: string
extension:
type: string
type: object
handler.createEndpointRequest:
properties:
codecs:
Expand Down Expand Up @@ -43,6 +57,31 @@ info:
title: Asterisk Administration API
version: "1.0"
paths:
/bouncer:
post:
consumes:
- application/json
parameters:
- description: Action to be reviewed
in: body
name: payload
required: true
schema:
$ref: '#/definitions/handler.AuthorizationRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/bouncer.Response'
"400":
description: Bad Request
"500":
description: Internal Server Error
summary: Determine whether the specified action (call) is allowed or not.
tags:
- bouncer
/endpoint:
post:
consumes:
Expand Down
43 changes: 43 additions & 0 deletions internal/bouncer/bouncer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package bouncer

import (
"context"
"github.com/crazybolillo/eryth/internal/db"
"github.com/crazybolillo/eryth/internal/sqlc"
"github.com/jackc/pgx/v5"
"log/slog"
)

type Response struct {
Allow bool `json:"allow"`
Destination string `json:"destination"`
}

type Bouncer struct {
*pgx.Conn
}

func (b *Bouncer) Check(ctx context.Context, endpoint, dialed string) Response {
result := Response{
Allow: false,
Destination: "",
}

tx, err := b.Begin(ctx)
if err != nil {
slog.Error("Unable to start transaction", slog.String("reason", err.Error()))
return result
}

queries := sqlc.New(tx)
destination, err := queries.GetEndpointByExtension(ctx, db.Text(dialed))
if err != nil {
slog.Error("Failed to retrieve endpoint", slog.String("dialed", dialed), slog.String("reason", err.Error()))
return result
}

return Response{
Allow: true,
Destination: destination,
}
}
63 changes: 63 additions & 0 deletions internal/handler/authorization.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package handler

import (
"context"
"encoding/json"
"github.com/crazybolillo/eryth/internal/bouncer"
"github.com/go-chi/chi/v5"
"log/slog"
"net/http"
)

type CallBouncer interface {
Check(ctx context.Context, endpoint, dialed string) bouncer.Response
}

type Authorization struct {
Bouncer CallBouncer
}

type AuthorizationRequest struct {
From string `json:"endpoint"`
Extension string `json:"extension"`
}

func (e *Authorization) Router() chi.Router {
r := chi.NewRouter()
r.Post("/", e.post)

return r
}

// @Summary Determine whether the specified action (call) is allowed or not.
// @Accept json
// @Produce json
// @Param payload body AuthorizationRequest true "Action to be reviewed"
// @Success 200 {object} bouncer.Response
// @Failure 400
// @Failure 500
// @Tags bouncer
// @Router /bouncer [post]
func (e *Authorization) post(w http.ResponseWriter, r *http.Request) {
var payload AuthorizationRequest
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&payload)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}

response := e.Bouncer.Check(r.Context(), payload.From, payload.Extension)
content, err := json.Marshal(response)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "application/json")
_, err = w.Write(content)
if err != nil {
slog.Error("Failed to write response", slog.String("path", r.URL.Path), slog.String("reason", err.Error()))
}
w.WriteHeader(http.StatusOK)
}
29 changes: 25 additions & 4 deletions internal/sqlc/queries.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 15 additions & 2 deletions queries.sql
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,26 @@ DELETE FROM ps_auths WHERE id = $1;

-- name: ListEndpoints :many
SELECT
id, context, transport
pe.id, pe.context, ee.extension
FROM
ps_endpoints
ps_endpoints pe
LEFT JOIN
ery_extension ee
ON ee.endpoint_id = pe.sid
LIMIT $1;

-- name: NewExtension :exec
INSERT INTO ery_extension
(endpoint_id, extension)
VALUES
($1, $2);

-- name: GetEndpointByExtension :one
SELECT
ps_endpoints.id
FROM
ps_endpoints
INNER JOIN
ery_extension ee on ps_endpoints.sid = ee.endpoint_id
WHERE
ee.extension = $1;

0 comments on commit 57793bb

Please sign in to comment.