forked from b3scale/b3scale
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapi.go
178 lines (152 loc) · 4.57 KB
/
api.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package api
/*
B3Scale API v1
Administrative API for B3Scale. See /docs/rest_api.md for
details.
*/
import (
"context"
"errors"
"net/http"
"strings"
"github.com/golang-jwt/jwt"
"github.com/jackc/pgx/v4/pgxpool"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/rs/zerolog/log"
"github.com/b3scale/b3scale/pkg/config"
"github.com/b3scale/b3scale/pkg/store"
"github.com/b3scale/b3scale/pkg/store/schema"
)
// Errors
var (
// ErrMissingJWTSecret will be returned if a JWT secret
// could not be found in the environment.
ErrMissingJWTSecret = errors.New("missing JWT secret")
)
const (
// PrefixInternalID indicates that the ID is an 'internal' ID.
PrefixInternalID = "internal:"
)
// InternalMeetingID returns the internal id for
// accessing via the API.
func InternalMeetingID(id string) string {
return PrefixInternalID + id
}
// API extends the context and provides methods
// for handling the current user.
type API struct {
// Authorization
Scopes []string
Ref string
// Database
Conn *pgxpool.Conn
echo.Context
}
// HasScope checks if the authentication scope claim
// contains a scope by name.
// The scope claim is a space separated list of scopes
// according to RFC8693, Section 4.2, (OAuth 2).
func (api *API) HasScope(s string) (found bool) {
for _, sc := range api.Scopes {
if sc == s {
return true
}
}
return false
}
// Ctx is a shortcut to access the request context
func (api *API) Ctx() context.Context {
return api.Request().Context()
}
// ParamID is a shortcut to access the ID parameter.
// If the parameter is prefixed with `internal:`, the
// prefix will be stripped and the ID will be returned.
func (api *API) ParamID() (string, bool) {
id := api.Param("id")
if strings.HasPrefix(id, PrefixInternalID) {
return id[len(PrefixInternalID):], true
}
return id, false
}
// ContextMiddleware initializes the context with
// auth information and a database connection.
func ContextMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
ctx := c.Request().Context()
// Add authorization to context
user := c.Get("user").(*jwt.Token)
claims := user.Claims.(*AuthClaims)
scopes := strings.Split(claims.Scope, " ")
ref := claims.StandardClaims.Subject
// Acquire connection
conn, err := store.Acquire(ctx)
if err != nil {
return err
}
defer conn.Release()
// Create API context
ac := &API{
Scopes: scopes,
Conn: conn,
Ref: ref,
Context: c,
}
return next(ac)
}
}
// Init sets up a group with authentication
// for a restful management interface.
func Init(e *echo.Echo) error {
// Initialize JWT middleware config
jwtConfig, err := NewAPIJWTConfig()
if err != nil {
return err
}
// Register routes
log.Info().Str("path", "/api/v1").Msg("initializing http api v1")
v1 := e.Group("/api/v1")
// API Auth and Context Middlewares
v1.Use(middleware.JWTWithConfig(jwtConfig))
v1.Use(ErrorHandler)
v1.Use(ContextMiddleware)
// Status
v1.GET("", Endpoint(apiStatusShow))
v1.GET("/status", Endpoint(apiStatusShow))
// API resources
ResourceFrontends.Mount(v1, "/frontends")
ResourceBackends.Mount(v1, "/backends")
ResourceMeetings.Mount(v1, "/meetings")
ResourceCommands.Mount(v1, "/commands")
ResourceRecordingsImport.Mount(v1, "/recordings-import")
ResourceAgentRPC.Mount(v1, "/agent/rpc")
ResourceAgentBackend.Mount(v1, "/agent/backend")
ResourceAgentHeartbeat.Mount(v1, "/agent/heartbeat")
ResourceCtlMigrate.Mount(v1, "/ctrl/migrate")
return nil
}
// StatusResponse returns information about the
// API implementation and the current user.
type StatusResponse struct {
Version string `json:"version" doc:"The current b3scale server version."`
Build string `json:"build" doc:"Build identifier of the server."`
API string `json:"api" doc:"The API version." example:"v1"`
AccountRef string `json:"account_ref" doc:"The currently authenticated subject."`
IsAdmin bool `json:"is_admin" doc:"True if the subject has admin privileges."`
Database *schema.Status `json:"database" doc:"Status of the database" api:"SchemaStatus"`
}
// apiStatusShow will respond with the api version and b3scale
// version.
func apiStatusShow(ctx context.Context, api *API) error {
dbURL := config.EnvOpt(config.EnvDbURL, config.EnvDbURLDefault)
m := schema.NewManager(dbURL)
status := &StatusResponse{
Version: config.Version,
Build: config.Build,
API: "v1",
AccountRef: api.Ref,
IsAdmin: api.HasScope(ScopeAdmin),
Database: m.Status(ctx),
}
return api.JSON(http.StatusOK, status)
}