Files
livekit/pkg/service/auth.go
David Zhao d9059f4f3b Do not accept websocket connection if response not received (#923)
When the instance handling the signal request did not respond to the
initial connection, we will fail the connection attempt instead of
having it hang forever.
2022-08-16 19:57:41 -07:00

168 lines
3.8 KiB
Go

package service
import (
"context"
"errors"
"net/http"
"strings"
"github.com/twitchtv/twirp"
"github.com/livekit/protocol/auth"
"github.com/livekit/protocol/livekit"
)
const (
authorizationHeader = "Authorization"
bearerPrefix = "Bearer "
accessTokenParam = "access_token"
)
type grantsKey struct{}
var (
ErrPermissionDenied = errors.New("permissions denied")
ErrMissingAuthorization = errors.New("invalid authorization header. Must start with " + bearerPrefix)
ErrInvalidAuthorizationToken = errors.New("invalid authorization token")
)
// authentication middleware
type APIKeyAuthMiddleware struct {
provider auth.KeyProvider
}
func NewAPIKeyAuthMiddleware(provider auth.KeyProvider) *APIKeyAuthMiddleware {
return &APIKeyAuthMiddleware{
provider: provider,
}
}
func (m *APIKeyAuthMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
if r.URL != nil && r.URL.Path == "/rtc/validate" {
w.Header().Set("Access-Control-Allow-Origin", "*")
}
authHeader := r.Header.Get(authorizationHeader)
var authToken string
if authHeader != "" {
if !strings.HasPrefix(authHeader, bearerPrefix) {
handleError(w, http.StatusUnauthorized, ErrMissingAuthorization)
return
}
authToken = authHeader[len(bearerPrefix):]
} else {
// attempt to find from request header
authToken = r.FormValue(accessTokenParam)
}
if authToken != "" {
v, err := auth.ParseAPIToken(authToken)
if err != nil {
handleError(w, http.StatusUnauthorized, ErrInvalidAuthorizationToken)
return
}
secret := m.provider.GetSecret(v.APIKey())
if secret == "" {
handleError(w, http.StatusUnauthorized, errors.New("invalid API key: "+v.APIKey()))
return
}
grants, err := v.Verify(secret)
if err != nil {
handleError(w, http.StatusUnauthorized, errors.New("invalid token: "+authToken+", error: "+err.Error()))
return
}
// set grants in context
ctx := r.Context()
r = r.WithContext(context.WithValue(ctx, grantsKey{}, grants))
}
next.ServeHTTP(w, r)
}
func GetGrants(ctx context.Context) *auth.ClaimGrants {
val := ctx.Value(grantsKey{})
claims, ok := val.(*auth.ClaimGrants)
if !ok {
return nil
}
return claims
}
func WithGrants(ctx context.Context, grants *auth.ClaimGrants) context.Context {
return context.WithValue(ctx, grantsKey{}, grants)
}
func SetAuthorizationToken(r *http.Request, token string) {
r.Header.Set(authorizationHeader, bearerPrefix+token)
}
func EnsureJoinPermission(ctx context.Context) (name livekit.RoomName, err error) {
claims := GetGrants(ctx)
if claims == nil || claims.Video == nil {
err = ErrPermissionDenied
return
}
if claims.Video.RoomJoin {
name = livekit.RoomName(claims.Video.Room)
} else {
err = ErrPermissionDenied
}
return
}
func EnsureAdminPermission(ctx context.Context, room livekit.RoomName) error {
claims := GetGrants(ctx)
if claims == nil || claims.Video == nil {
return ErrPermissionDenied
}
if !claims.Video.RoomAdmin || room != livekit.RoomName(claims.Video.Room) {
return ErrPermissionDenied
}
return nil
}
func EnsureCreatePermission(ctx context.Context) error {
claims := GetGrants(ctx)
if claims == nil {
return ErrPermissionDenied
}
if claims.Video.RoomCreate {
return nil
}
return ErrPermissionDenied
}
func EnsureListPermission(ctx context.Context) error {
claims := GetGrants(ctx)
if claims == nil {
return ErrPermissionDenied
}
if claims.Video.RoomList {
return nil
}
return ErrPermissionDenied
}
func EnsureRecordPermission(ctx context.Context) error {
claims := GetGrants(ctx)
if claims == nil || !claims.Video.RoomRecord {
return ErrPermissionDenied
}
return nil
}
// wraps authentication errors around Twirp
func twirpAuthError(err error) error {
return twirp.NewError(twirp.Unauthenticated, err.Error())
}