mirror of
https://github.com/livekit/livekit.git
synced 2026-10-23 13:02:53 +00:00
simplify token issuer/verifier interface
This commit is contained in:
1 parent
353f35443a
commit
46dadc25c2
13 files changed
+245
-338
No files matched your search
@@ -0,0 +1,63 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
)
|
||||
|
||||
// Signer that produces token signed with API key and secret
|
||||
type AccessToken struct {
|
||||
apiKey string
|
||||
secret string
|
||||
identity string
|
||||
grant *VideoGrant
|
||||
validFor time.Duration
|
||||
}
|
||||
|
||||
func NewAccessToken(key string, secret string) *AccessToken {
|
||||
return &AccessToken{
|
||||
apiKey: key,
|
||||
secret: secret,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *AccessToken) SetIdentity(identity string) *AccessToken {
|
||||
t.identity = identity
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *AccessToken) SetValidFor(duration time.Duration) *AccessToken {
|
||||
t.validFor = duration
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *AccessToken) AddGrant(grant *VideoGrant) *AccessToken {
|
||||
t.grant = grant
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *AccessToken) ToJWT() (string, error) {
|
||||
if t.apiKey == "" || t.secret == "" {
|
||||
return "", ErrKeysMissing
|
||||
}
|
||||
|
||||
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.HS256, Key: []byte(t.secret)},
|
||||
(&jose.SignerOptions{}).WithType("JWT"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
cl := jwt.Claims{
|
||||
Issuer: t.apiKey,
|
||||
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||
Expiry: jwt.NewNumericDate(time.Now().Add(t.validFor)),
|
||||
ID: t.identity,
|
||||
}
|
||||
grants := &ClaimGrants{}
|
||||
if t.grant != nil {
|
||||
grants.Video = t.grant
|
||||
}
|
||||
return jwt.Signed(sig).Claims(cl).Claims(grants).CompactSerialize()
|
||||
}
|
||||
@@ -14,31 +14,33 @@ import (
|
||||
|
||||
func TestAPIIssuer(t *testing.T) {
|
||||
t.Run("keys must be set", func(t *testing.T) {
|
||||
s := auth.NewAPIKeyTokenIssuer("", "")
|
||||
_, err := s.CreateToken(&auth.GrantClaims{}, time.Hour)
|
||||
token := auth.NewAccessToken("", "")
|
||||
_, err := token.ToJWT()
|
||||
assert.Equal(t, auth.ErrKeysMissing, err)
|
||||
})
|
||||
|
||||
t.Run("generates a decodeable key", func(t *testing.T) {
|
||||
apiKey, secret := apiKeypair()
|
||||
//fmt.Println(apiKey, secret)
|
||||
s := auth.NewAPIKeyTokenIssuer(apiKey, secret)
|
||||
claims := auth.GrantClaims{RoomJoin: true, Room: "myroom"}
|
||||
raw, err := s.CreateToken(&claims, time.Hour)
|
||||
videoGrant := &auth.VideoGrant{RoomJoin: true, Room: "myroom"}
|
||||
at := auth.NewAccessToken(apiKey, secret).
|
||||
AddGrant(videoGrant).
|
||||
SetValidFor(time.Minute * 5).
|
||||
SetIdentity("user")
|
||||
value, err := at.ToJWT()
|
||||
//fmt.Println(raw)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Len(t, strings.Split(raw, "."), 3)
|
||||
assert.Len(t, strings.Split(value, "."), 3)
|
||||
|
||||
// ensure it's a valid JWT
|
||||
token, err := jwt.ParseSigned(raw)
|
||||
token, err := jwt.ParseSigned(value)
|
||||
assert.NoError(t, err)
|
||||
|
||||
decodedGrant := auth.GrantClaims{}
|
||||
decodedGrant := auth.ClaimGrants{}
|
||||
err = token.UnsafeClaimsWithoutVerification(&decodedGrant)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, &claims, &decodedGrant)
|
||||
assert.EqualValues(t, videoGrant, decodedGrant.Video)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -19,14 +19,14 @@ type FakeKeyProvider struct {
|
||||
getSecretReturnsOnCall map[int]struct {
|
||||
result1 string
|
||||
}
|
||||
KeyCountStub func() int
|
||||
keyCountMutex sync.RWMutex
|
||||
keyCountArgsForCall []struct {
|
||||
NumKeysStub func() int
|
||||
numKeysMutex sync.RWMutex
|
||||
numKeysArgsForCall []struct {
|
||||
}
|
||||
keyCountReturns struct {
|
||||
numKeysReturns struct {
|
||||
result1 int
|
||||
}
|
||||
keyCountReturnsOnCall map[int]struct {
|
||||
numKeysReturnsOnCall map[int]struct {
|
||||
result1 int
|
||||
}
|
||||
invocations map[string][][]interface{}
|
||||
@@ -95,14 +95,14 @@ func (fake *FakeKeyProvider) GetSecretReturnsOnCall(i int, result1 string) {
|
||||
}
|
||||
|
||||
func (fake *FakeKeyProvider) NumKeys() int {
|
||||
fake.keyCountMutex.Lock()
|
||||
ret, specificReturn := fake.keyCountReturnsOnCall[len(fake.keyCountArgsForCall)]
|
||||
fake.keyCountArgsForCall = append(fake.keyCountArgsForCall, struct {
|
||||
fake.numKeysMutex.Lock()
|
||||
ret, specificReturn := fake.numKeysReturnsOnCall[len(fake.numKeysArgsForCall)]
|
||||
fake.numKeysArgsForCall = append(fake.numKeysArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.KeyCountStub
|
||||
fakeReturns := fake.keyCountReturns
|
||||
stub := fake.NumKeysStub
|
||||
fakeReturns := fake.numKeysReturns
|
||||
fake.recordInvocation("NumKeys", []interface{}{})
|
||||
fake.keyCountMutex.Unlock()
|
||||
fake.numKeysMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub()
|
||||
}
|
||||
@@ -112,37 +112,37 @@ func (fake *FakeKeyProvider) NumKeys() int {
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeKeyProvider) KeyCountCallCount() int {
|
||||
fake.keyCountMutex.RLock()
|
||||
defer fake.keyCountMutex.RUnlock()
|
||||
return len(fake.keyCountArgsForCall)
|
||||
func (fake *FakeKeyProvider) NumKeysCallCount() int {
|
||||
fake.numKeysMutex.RLock()
|
||||
defer fake.numKeysMutex.RUnlock()
|
||||
return len(fake.numKeysArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeKeyProvider) KeyCountCalls(stub func() int) {
|
||||
fake.keyCountMutex.Lock()
|
||||
defer fake.keyCountMutex.Unlock()
|
||||
fake.KeyCountStub = stub
|
||||
func (fake *FakeKeyProvider) NumKeysCalls(stub func() int) {
|
||||
fake.numKeysMutex.Lock()
|
||||
defer fake.numKeysMutex.Unlock()
|
||||
fake.NumKeysStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeKeyProvider) KeyCountReturns(result1 int) {
|
||||
fake.keyCountMutex.Lock()
|
||||
defer fake.keyCountMutex.Unlock()
|
||||
fake.KeyCountStub = nil
|
||||
fake.keyCountReturns = struct {
|
||||
func (fake *FakeKeyProvider) NumKeysReturns(result1 int) {
|
||||
fake.numKeysMutex.Lock()
|
||||
defer fake.numKeysMutex.Unlock()
|
||||
fake.NumKeysStub = nil
|
||||
fake.numKeysReturns = struct {
|
||||
result1 int
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeKeyProvider) KeyCountReturnsOnCall(i int, result1 int) {
|
||||
fake.keyCountMutex.Lock()
|
||||
defer fake.keyCountMutex.Unlock()
|
||||
fake.KeyCountStub = nil
|
||||
if fake.keyCountReturnsOnCall == nil {
|
||||
fake.keyCountReturnsOnCall = make(map[int]struct {
|
||||
func (fake *FakeKeyProvider) NumKeysReturnsOnCall(i int, result1 int) {
|
||||
fake.numKeysMutex.Lock()
|
||||
defer fake.numKeysMutex.Unlock()
|
||||
fake.NumKeysStub = nil
|
||||
if fake.numKeysReturnsOnCall == nil {
|
||||
fake.numKeysReturnsOnCall = make(map[int]struct {
|
||||
result1 int
|
||||
})
|
||||
}
|
||||
fake.keyCountReturnsOnCall[i] = struct {
|
||||
fake.numKeysReturnsOnCall[i] = struct {
|
||||
result1 int
|
||||
}{result1}
|
||||
}
|
||||
@@ -152,8 +152,8 @@ func (fake *FakeKeyProvider) Invocations() map[string][][]interface{} {
|
||||
defer fake.invocationsMutex.RUnlock()
|
||||
fake.getSecretMutex.RLock()
|
||||
defer fake.getSecretMutex.RUnlock()
|
||||
fake.keyCountMutex.RLock()
|
||||
defer fake.keyCountMutex.RUnlock()
|
||||
fake.numKeysMutex.RLock()
|
||||
defer fake.numKeysMutex.RUnlock()
|
||||
copiedInvocations := map[string][][]interface{}{}
|
||||
for key, value := range fake.invocations {
|
||||
copiedInvocations[key] = value
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
// Code generated by counterfeiter. DO NOT EDIT.
|
||||
package authfakes
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/auth"
|
||||
)
|
||||
|
||||
type FakeTokenIssuer struct {
|
||||
CreateTokenStub func(*auth.GrantClaims, time.Duration) (string, error)
|
||||
createTokenMutex sync.RWMutex
|
||||
createTokenArgsForCall []struct {
|
||||
arg1 *auth.GrantClaims
|
||||
arg2 time.Duration
|
||||
}
|
||||
createTokenReturns struct {
|
||||
result1 string
|
||||
result2 error
|
||||
}
|
||||
createTokenReturnsOnCall map[int]struct {
|
||||
result1 string
|
||||
result2 error
|
||||
}
|
||||
invocations map[string][][]interface{}
|
||||
invocationsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (fake *FakeTokenIssuer) CreateToken(arg1 *auth.GrantClaims, arg2 time.Duration) (string, error) {
|
||||
fake.createTokenMutex.Lock()
|
||||
ret, specificReturn := fake.createTokenReturnsOnCall[len(fake.createTokenArgsForCall)]
|
||||
fake.createTokenArgsForCall = append(fake.createTokenArgsForCall, struct {
|
||||
arg1 *auth.GrantClaims
|
||||
arg2 time.Duration
|
||||
}{arg1, arg2})
|
||||
stub := fake.CreateTokenStub
|
||||
fakeReturns := fake.createTokenReturns
|
||||
fake.recordInvocation("CreateToken", []interface{}{arg1, arg2})
|
||||
fake.createTokenMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
}
|
||||
|
||||
func (fake *FakeTokenIssuer) CreateTokenCallCount() int {
|
||||
fake.createTokenMutex.RLock()
|
||||
defer fake.createTokenMutex.RUnlock()
|
||||
return len(fake.createTokenArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeTokenIssuer) CreateTokenCalls(stub func(*auth.GrantClaims, time.Duration) (string, error)) {
|
||||
fake.createTokenMutex.Lock()
|
||||
defer fake.createTokenMutex.Unlock()
|
||||
fake.CreateTokenStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeTokenIssuer) CreateTokenArgsForCall(i int) (*auth.GrantClaims, time.Duration) {
|
||||
fake.createTokenMutex.RLock()
|
||||
defer fake.createTokenMutex.RUnlock()
|
||||
argsForCall := fake.createTokenArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeTokenIssuer) CreateTokenReturns(result1 string, result2 error) {
|
||||
fake.createTokenMutex.Lock()
|
||||
defer fake.createTokenMutex.Unlock()
|
||||
fake.CreateTokenStub = nil
|
||||
fake.createTokenReturns = struct {
|
||||
result1 string
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeTokenIssuer) CreateTokenReturnsOnCall(i int, result1 string, result2 error) {
|
||||
fake.createTokenMutex.Lock()
|
||||
defer fake.createTokenMutex.Unlock()
|
||||
fake.CreateTokenStub = nil
|
||||
if fake.createTokenReturnsOnCall == nil {
|
||||
fake.createTokenReturnsOnCall = make(map[int]struct {
|
||||
result1 string
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.createTokenReturnsOnCall[i] = struct {
|
||||
result1 string
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeTokenIssuer) Invocations() map[string][][]interface{} {
|
||||
fake.invocationsMutex.RLock()
|
||||
defer fake.invocationsMutex.RUnlock()
|
||||
fake.createTokenMutex.RLock()
|
||||
defer fake.createTokenMutex.RUnlock()
|
||||
copiedInvocations := map[string][][]interface{}{}
|
||||
for key, value := range fake.invocations {
|
||||
copiedInvocations[key] = value
|
||||
}
|
||||
return copiedInvocations
|
||||
}
|
||||
|
||||
func (fake *FakeTokenIssuer) recordInvocation(key string, args []interface{}) {
|
||||
fake.invocationsMutex.Lock()
|
||||
defer fake.invocationsMutex.Unlock()
|
||||
if fake.invocations == nil {
|
||||
fake.invocations = map[string][][]interface{}{}
|
||||
}
|
||||
if fake.invocations[key] == nil {
|
||||
fake.invocations[key] = [][]interface{}{}
|
||||
}
|
||||
fake.invocations[key] = append(fake.invocations[key], args)
|
||||
}
|
||||
|
||||
var _ auth.TokenIssuer = new(FakeTokenIssuer)
|
||||
@@ -8,33 +8,98 @@ import (
|
||||
)
|
||||
|
||||
type FakeTokenVerifier struct {
|
||||
VerifyStub func() (*auth.GrantClaims, error)
|
||||
IdentityStub func() string
|
||||
identityMutex sync.RWMutex
|
||||
identityArgsForCall []struct {
|
||||
}
|
||||
identityReturns struct {
|
||||
result1 string
|
||||
}
|
||||
identityReturnsOnCall map[int]struct {
|
||||
result1 string
|
||||
}
|
||||
VerifyStub func(interface{}) (*auth.VideoGrant, error)
|
||||
verifyMutex sync.RWMutex
|
||||
verifyArgsForCall []struct {
|
||||
arg1 interface{}
|
||||
}
|
||||
verifyReturns struct {
|
||||
result1 *auth.GrantClaims
|
||||
result1 *auth.VideoGrant
|
||||
result2 error
|
||||
}
|
||||
verifyReturnsOnCall map[int]struct {
|
||||
result1 *auth.GrantClaims
|
||||
result1 *auth.VideoGrant
|
||||
result2 error
|
||||
}
|
||||
invocations map[string][][]interface{}
|
||||
invocationsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (fake *FakeTokenVerifier) Verify() (*auth.GrantClaims, error) {
|
||||
func (fake *FakeTokenVerifier) Identity() string {
|
||||
fake.identityMutex.Lock()
|
||||
ret, specificReturn := fake.identityReturnsOnCall[len(fake.identityArgsForCall)]
|
||||
fake.identityArgsForCall = append(fake.identityArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.IdentityStub
|
||||
fakeReturns := fake.identityReturns
|
||||
fake.recordInvocation("Identity", []interface{}{})
|
||||
fake.identityMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeTokenVerifier) IdentityCallCount() int {
|
||||
fake.identityMutex.RLock()
|
||||
defer fake.identityMutex.RUnlock()
|
||||
return len(fake.identityArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeTokenVerifier) IdentityCalls(stub func() string) {
|
||||
fake.identityMutex.Lock()
|
||||
defer fake.identityMutex.Unlock()
|
||||
fake.IdentityStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeTokenVerifier) IdentityReturns(result1 string) {
|
||||
fake.identityMutex.Lock()
|
||||
defer fake.identityMutex.Unlock()
|
||||
fake.IdentityStub = nil
|
||||
fake.identityReturns = struct {
|
||||
result1 string
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeTokenVerifier) IdentityReturnsOnCall(i int, result1 string) {
|
||||
fake.identityMutex.Lock()
|
||||
defer fake.identityMutex.Unlock()
|
||||
fake.IdentityStub = nil
|
||||
if fake.identityReturnsOnCall == nil {
|
||||
fake.identityReturnsOnCall = make(map[int]struct {
|
||||
result1 string
|
||||
})
|
||||
}
|
||||
fake.identityReturnsOnCall[i] = struct {
|
||||
result1 string
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeTokenVerifier) Verify(arg1 interface{}) (*auth.VideoGrant, error) {
|
||||
fake.verifyMutex.Lock()
|
||||
ret, specificReturn := fake.verifyReturnsOnCall[len(fake.verifyArgsForCall)]
|
||||
fake.verifyArgsForCall = append(fake.verifyArgsForCall, struct {
|
||||
}{})
|
||||
arg1 interface{}
|
||||
}{arg1})
|
||||
stub := fake.VerifyStub
|
||||
fakeReturns := fake.verifyReturns
|
||||
fake.recordInvocation("Verify", []interface{}{})
|
||||
fake.recordInvocation("Verify", []interface{}{arg1})
|
||||
fake.verifyMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub()
|
||||
return stub(arg1)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
@@ -48,34 +113,41 @@ func (fake *FakeTokenVerifier) VerifyCallCount() int {
|
||||
return len(fake.verifyArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeTokenVerifier) VerifyCalls(stub func() (*auth.GrantClaims, error)) {
|
||||
func (fake *FakeTokenVerifier) VerifyCalls(stub func(interface{}) (*auth.VideoGrant, error)) {
|
||||
fake.verifyMutex.Lock()
|
||||
defer fake.verifyMutex.Unlock()
|
||||
fake.VerifyStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeTokenVerifier) VerifyReturns(result1 *auth.GrantClaims, result2 error) {
|
||||
func (fake *FakeTokenVerifier) VerifyArgsForCall(i int) interface{} {
|
||||
fake.verifyMutex.RLock()
|
||||
defer fake.verifyMutex.RUnlock()
|
||||
argsForCall := fake.verifyArgsForCall[i]
|
||||
return argsForCall.arg1
|
||||
}
|
||||
|
||||
func (fake *FakeTokenVerifier) VerifyReturns(result1 *auth.VideoGrant, result2 error) {
|
||||
fake.verifyMutex.Lock()
|
||||
defer fake.verifyMutex.Unlock()
|
||||
fake.VerifyStub = nil
|
||||
fake.verifyReturns = struct {
|
||||
result1 *auth.GrantClaims
|
||||
result1 *auth.VideoGrant
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeTokenVerifier) VerifyReturnsOnCall(i int, result1 *auth.GrantClaims, result2 error) {
|
||||
func (fake *FakeTokenVerifier) VerifyReturnsOnCall(i int, result1 *auth.VideoGrant, result2 error) {
|
||||
fake.verifyMutex.Lock()
|
||||
defer fake.verifyMutex.Unlock()
|
||||
fake.VerifyStub = nil
|
||||
if fake.verifyReturnsOnCall == nil {
|
||||
fake.verifyReturnsOnCall = make(map[int]struct {
|
||||
result1 *auth.GrantClaims
|
||||
result1 *auth.VideoGrant
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.verifyReturnsOnCall[i] = struct {
|
||||
result1 *auth.GrantClaims
|
||||
result1 *auth.VideoGrant
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
@@ -83,6 +155,8 @@ func (fake *FakeTokenVerifier) VerifyReturnsOnCall(i int, result1 *auth.GrantCla
|
||||
func (fake *FakeTokenVerifier) Invocations() map[string][][]interface{} {
|
||||
fake.invocationsMutex.RLock()
|
||||
defer fake.invocationsMutex.RUnlock()
|
||||
fake.identityMutex.RLock()
|
||||
defer fake.identityMutex.RUnlock()
|
||||
fake.verifyMutex.RLock()
|
||||
defer fake.verifyMutex.RUnlock()
|
||||
copiedInvocations := map[string][][]interface{}{}
|
||||
|
||||
+6
-2
@@ -1,7 +1,11 @@
|
||||
package auth
|
||||
|
||||
type GrantClaims struct {
|
||||
type VideoGrant struct {
|
||||
RoomCreate bool `json:"room_create,omitempty"`
|
||||
RoomJoin bool `json:"room_join,omitempty"`
|
||||
Room string `json:"room"`
|
||||
Room string `json:"room,omitempty"`
|
||||
}
|
||||
|
||||
type ClaimGrants struct {
|
||||
Video *VideoGrant `json:"video,omitempty"`
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate
|
||||
@@ -11,14 +10,18 @@ var (
|
||||
ErrKeysMissing = errors.New("missing API key or secret key")
|
||||
)
|
||||
|
||||
//counterfeiter:generate . TokenIssuer
|
||||
type TokenIssuer interface {
|
||||
CreateToken(claims *GrantClaims, validFor time.Duration) (string, error)
|
||||
}
|
||||
////counterfeiter:generate . AccessToken
|
||||
//type AccessToken interface {
|
||||
// SetExpiration(time.Duration) AccessToken
|
||||
// Identity(string) AccessToken
|
||||
// AddGrant(*VideoGrant) AccessToken
|
||||
// ToJWT() (string, error)
|
||||
//}
|
||||
|
||||
//counterfeiter:generate . TokenVerifier
|
||||
type TokenVerifier interface {
|
||||
Verify() (*GrantClaims, error)
|
||||
Identity() string
|
||||
Verify(key interface{}) (*VideoGrant, error)
|
||||
}
|
||||
|
||||
//counterfeiter:generate . KeyProvider
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
)
|
||||
|
||||
// Signer that produces token signed with API key and secret
|
||||
type APIKeyTokenIssuer struct {
|
||||
APIKey string
|
||||
SecretKey string
|
||||
}
|
||||
|
||||
func NewAPIKeyTokenIssuer(key string, secret string) *APIKeyTokenIssuer {
|
||||
return &APIKeyTokenIssuer{
|
||||
APIKey: key,
|
||||
SecretKey: secret,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *APIKeyTokenIssuer) CreateToken(claims *GrantClaims, validFor time.Duration) (string, error) {
|
||||
if s.APIKey == "" || s.SecretKey == "" {
|
||||
return "", ErrKeysMissing
|
||||
}
|
||||
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.HS256, Key: []byte(s.SecretKey)},
|
||||
(&jose.SignerOptions{}).WithType("JWT"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
cl := jwt.Claims{
|
||||
Issuer: s.APIKey,
|
||||
Subject: s.APIKey,
|
||||
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||
Expiry: jwt.NewNumericDate(time.Now().Add(validFor)),
|
||||
}
|
||||
|
||||
return jwt.Signed(sig).Claims(cl).Claims(claims).CompactSerialize()
|
||||
}
|
||||
+16
-12
@@ -7,9 +7,9 @@ import (
|
||||
)
|
||||
|
||||
type APIKeyTokenVerifier struct {
|
||||
token *jwt.JSONWebToken
|
||||
apiKey string
|
||||
secretKey string
|
||||
token *jwt.JSONWebToken
|
||||
identity string
|
||||
apiKey string
|
||||
}
|
||||
|
||||
func ParseAPIToken(raw string) (*APIKeyTokenVerifier, error) {
|
||||
@@ -24,8 +24,9 @@ func ParseAPIToken(raw string) (*APIKeyTokenVerifier, error) {
|
||||
}
|
||||
|
||||
return &APIKeyTokenVerifier{
|
||||
token: tok,
|
||||
apiKey: out.Issuer,
|
||||
token: tok,
|
||||
apiKey: out.Issuer,
|
||||
identity: out.ID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -34,21 +35,24 @@ func (v *APIKeyTokenVerifier) APIKey() string {
|
||||
return v.apiKey
|
||||
}
|
||||
|
||||
func (v *APIKeyTokenVerifier) SetSecretKey(secret string) {
|
||||
v.secretKey = secret
|
||||
func (v *APIKeyTokenVerifier) Identity() string {
|
||||
return v.identity
|
||||
}
|
||||
|
||||
func (v *APIKeyTokenVerifier) Verify() (*GrantClaims, error) {
|
||||
if v.secretKey == "" {
|
||||
func (v *APIKeyTokenVerifier) Verify(key interface{}) (*VideoGrant, error) {
|
||||
if key == nil || key == "" {
|
||||
return nil, ErrKeysMissing
|
||||
}
|
||||
if s, ok := key.(string); ok {
|
||||
key = []byte(s)
|
||||
}
|
||||
out := jwt.Claims{}
|
||||
claims := GrantClaims{}
|
||||
if err := v.token.Claims([]byte(v.secretKey), &out, &claims); err != nil {
|
||||
claims := ClaimGrants{}
|
||||
if err := v.token.Claims(key, &out, &claims); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := out.Validate(jwt.Expected{Issuer: v.apiKey, Time: time.Now()}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &claims, nil
|
||||
return claims.Video, nil
|
||||
}
|
||||
+11
-10
@@ -18,34 +18,35 @@ func TestAPIVerifier(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, apiKey, v.APIKey())
|
||||
_, err = v.Verify()
|
||||
_, err = v.Verify("")
|
||||
assert.Error(t, err)
|
||||
|
||||
v.SetSecretKey("anothersecret")
|
||||
_, err = v.Verify()
|
||||
_, err = v.Verify("anothersecret")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("key has expired", func(t *testing.T) {
|
||||
v, err := auth.ParseAPIToken(accessToken)
|
||||
v.SetSecretKey(secret)
|
||||
|
||||
_, err = v.Verify()
|
||||
_, err = v.Verify(secret)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("unexpired token is verified", func(t *testing.T) {
|
||||
s := auth.NewAPIKeyTokenIssuer(apiKey, secret)
|
||||
claim := auth.GrantClaims{RoomCreate: true}
|
||||
authToken, err := s.CreateToken(&claim, time.Minute)
|
||||
claim := auth.VideoGrant{RoomCreate: true}
|
||||
at := auth.NewAccessToken(apiKey, secret).
|
||||
AddGrant(&claim).
|
||||
SetValidFor(time.Minute).
|
||||
SetIdentity("me")
|
||||
authToken, err := at.ToJWT()
|
||||
assert.NoError(t, err)
|
||||
|
||||
v, err := auth.ParseAPIToken(authToken)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, apiKey, v.APIKey())
|
||||
assert.Equal(t, "me", v.Identity())
|
||||
|
||||
v.SetSecretKey(secret)
|
||||
decoded, err := v.Verify()
|
||||
decoded, err := v.Verify(secret)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, &claim, decoded)
|
||||
})
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
package rtc
|
||||
|
||||
import (
|
||||
"github.com/pion/sdp/v3"
|
||||
"github.com/pion/webrtc/v3"
|
||||
)
|
||||
|
||||
const (
|
||||
mimeTypeH264 = "video/h264"
|
||||
mimeTypeOpus = "audio/opus"
|
||||
mimeTypeVP8 = "video/vp8"
|
||||
mimeTypeVP9 = "video/vp9"
|
||||
)
|
||||
|
||||
func createMediaEngine() (*webrtc.MediaEngine, error) {
|
||||
me := &webrtc.MediaEngine{}
|
||||
if err := me.RegisterCodec(webrtc.RTPCodecParameters{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: mimeTypeOpus, ClockRate: 48000, Channels: 2, SDPFmtpLine: "minptime=10;useinbandfec=1", RTCPFeedback: nil},
|
||||
PayloadType: 111,
|
||||
}, webrtc.RTPCodecTypeAudio); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
videoRTCPFeedback := []webrtc.RTCPFeedback{{"goog-remb", ""}, {"ccm", "fir"}, {"nack", ""}, {"nack", "pli"}}
|
||||
for _, codec := range []webrtc.RTPCodecParameters{
|
||||
{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: mimeTypeVP8, ClockRate: 90000, RTCPFeedback: videoRTCPFeedback},
|
||||
PayloadType: 96,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: mimeTypeVP9, ClockRate: 90000, SDPFmtpLine: "profile-id=0", RTCPFeedback: videoRTCPFeedback},
|
||||
PayloadType: 98,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: mimeTypeVP9, ClockRate: 90000, SDPFmtpLine: "profile-id=1", RTCPFeedback: videoRTCPFeedback},
|
||||
PayloadType: 100,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: mimeTypeH264, ClockRate: 90000, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f", RTCPFeedback: videoRTCPFeedback},
|
||||
PayloadType: 102,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: mimeTypeH264, ClockRate: 90000, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f", RTCPFeedback: videoRTCPFeedback},
|
||||
PayloadType: 127,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: mimeTypeH264, ClockRate: 90000, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", RTCPFeedback: videoRTCPFeedback},
|
||||
PayloadType: 125,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: mimeTypeH264, ClockRate: 90000, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f", RTCPFeedback: videoRTCPFeedback},
|
||||
PayloadType: 108,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: mimeTypeH264, ClockRate: 90000, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f", RTCPFeedback: videoRTCPFeedback},
|
||||
PayloadType: 127,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: mimeTypeH264, ClockRate: 90000, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640032", RTCPFeedback: videoRTCPFeedback},
|
||||
PayloadType: 123,
|
||||
},
|
||||
} {
|
||||
if err := me.RegisterCodec(codec, webrtc.RTPCodecTypeVideo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, extension := range []string{
|
||||
sdp.SDESMidURI,
|
||||
sdp.SDESRTPStreamIDURI,
|
||||
sdp.TransportCCURI,
|
||||
} {
|
||||
if err := me.RegisterHeaderExtension(webrtc.RTPHeaderExtensionCapability{URI: extension}, webrtc.RTPCodecTypeAudio); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := me.RegisterHeaderExtension(webrtc.RTPHeaderExtensionCapability{URI: extension}, webrtc.RTPCodecTypeVideo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return me, nil
|
||||
}
|
||||
+3
-5
@@ -58,9 +58,7 @@ func (m *APIKeyAuthMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request,
|
||||
return
|
||||
}
|
||||
|
||||
v.SetSecretKey(secret)
|
||||
|
||||
grants, err := v.Verify()
|
||||
grants, err := v.Verify(secret)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte("invalid token: " + err.Error()))
|
||||
@@ -75,8 +73,8 @@ func (m *APIKeyAuthMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request,
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func GetGrants(ctx context.Context) *auth.GrantClaims {
|
||||
claims, ok := ctx.Value(grantsKey).(*auth.GrantClaims)
|
||||
func GetGrants(ctx context.Context) *auth.VideoGrant {
|
||||
claims, ok := ctx.Value(grantsKey).(*auth.VideoGrant)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
@@ -20,16 +19,17 @@ func TestAuthMiddleware(t *testing.T) {
|
||||
provider.GetSecretReturns(secret)
|
||||
|
||||
m := service.NewAPIKeyAuthMiddleware(provider)
|
||||
var grants *auth.GrantClaims
|
||||
var grants *auth.VideoGrant
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
grants = service.GetGrants(r.Context())
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
orig := &auth.VideoGrant{Room: "abcdefg", RoomJoin: true}
|
||||
// ensure that the original claim could be retrieved
|
||||
issuer := auth.NewAPIKeyTokenIssuer(api, secret)
|
||||
orig := &auth.GrantClaims{Room: "abcdefg", RoomJoin: true}
|
||||
token, err := issuer.CreateToken(orig, time.Minute)
|
||||
at := auth.NewAccessToken(api, secret).
|
||||
AddGrant(orig)
|
||||
token, err := at.ToJWT()
|
||||
assert.NoError(t, err)
|
||||
|
||||
r := &http.Request{Header: http.Header{}}
|
||||
|
||||
Reference in new issue
Block a user