mirror of https://git.sr.ht/~sircmpwn/gql.sr.ht
Implement OAuth 2.0 bearer token w/scopes
This commit is contained in:
parent
0395c9720d
commit
8eb53f35aa
|
@ -50,7 +50,7 @@ const (
|
|||
)
|
||||
|
||||
const (
|
||||
AUTH_OAUTH = iota
|
||||
AUTH_OAUTH_LEGACY = iota
|
||||
AUTH_OAUTH2 = iota
|
||||
AUTH_COOKIE = iota
|
||||
AUTH_INTERNAL = iota
|
||||
|
@ -75,6 +75,7 @@ type User struct {
|
|||
|
||||
// Only filled out if AuthMethod == AUTH_OAUTH2
|
||||
OAuth2Token *OAuth2Token
|
||||
Access map[string]string
|
||||
}
|
||||
|
||||
func authError(w http.ResponseWriter, reason string, code int) {
|
||||
|
@ -404,6 +405,32 @@ func OAuth2(db *sql.DB, token string, hash [64]byte,
|
|||
user.AuthMethod = AUTH_OAUTH2
|
||||
user.OAuth2Token = ot
|
||||
|
||||
if ot.Scopes != "" {
|
||||
user.Access = make(map[string]string)
|
||||
for _, grant := range strings.Split(ot.Scopes, ",") {
|
||||
var (
|
||||
service string
|
||||
scope string
|
||||
access string
|
||||
)
|
||||
parts := strings.Split(grant, "/")
|
||||
if len(parts) != 2 {
|
||||
panic(errors.New("OAuth grant without service/scope format"))
|
||||
}
|
||||
service = parts[0]
|
||||
parts = strings.Split(parts[1], ":")
|
||||
scope = parts[0]
|
||||
if len(parts) == 1 {
|
||||
access = "RO"
|
||||
} else {
|
||||
access = parts[1]
|
||||
}
|
||||
if service == config.ServiceName(r.Context()) {
|
||||
user.Access[scope] = access
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), userCtxKey, &user)
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
|
@ -481,7 +508,7 @@ func LegacyOAuth(db *sql.DB, bearer string, hash [64]byte,
|
|||
return
|
||||
}
|
||||
|
||||
user.AuthMethod = AUTH_OAUTH
|
||||
user.AuthMethod = AUTH_OAUTH_LEGACY
|
||||
|
||||
ctx := context.WithValue(r.Context(), userCtxKey, &user)
|
||||
|
||||
|
|
|
@ -9,15 +9,18 @@ import (
|
|||
)
|
||||
|
||||
var configCtxKey = &contextKey{"config"}
|
||||
var serviceCtxKey = &contextKey{"name"}
|
||||
|
||||
type contextKey struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func Middleware(conf ini.File) func (next http.Handler) http.Handler {
|
||||
func Middleware(conf ini.File, service string) func (next http.Handler) http.Handler {
|
||||
svc := service
|
||||
return func (next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := context.WithValue(r.Context(), configCtxKey, conf)
|
||||
ctx = context.WithValue(ctx, serviceCtxKey, &svc)
|
||||
r = r.WithContext(ctx)
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
|
@ -31,3 +34,11 @@ func ForContext(ctx context.Context) ini.File {
|
|||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
func ServiceName(ctx context.Context) string {
|
||||
raw, ok := ctx.Value(serviceCtxKey).(*string)
|
||||
if !ok {
|
||||
panic(errors.New("Invalid config context"))
|
||||
}
|
||||
return *raw
|
||||
}
|
||||
|
|
|
@ -22,10 +22,30 @@ func Internal(ctx context.Context, obj interface{},
|
|||
func Access(ctx context.Context, obj interface{}, next graphql.Resolver,
|
||||
scope string, kind string) (interface{}, error) {
|
||||
|
||||
if auth.ForContext(ctx).AuthMethod == auth.AUTH_INTERNAL ||
|
||||
auth.ForContext(ctx).AuthMethod == auth.AUTH_COOKIE {
|
||||
authctx := auth.ForContext(ctx)
|
||||
|
||||
switch authctx.AuthMethod {
|
||||
case auth.AUTH_INTERNAL:
|
||||
case auth.AUTH_COOKIE:
|
||||
return next(ctx)
|
||||
case auth.AUTH_OAUTH_LEGACY:
|
||||
if kind == "RO" {
|
||||
// Only legacy tokens with "*" scopes ever get this far
|
||||
return next(ctx)
|
||||
}
|
||||
case auth.AUTH_OAUTH2:
|
||||
if authctx.Access == nil {
|
||||
return next(ctx)
|
||||
}
|
||||
if access, ok := authctx.Access[scope]; !ok {
|
||||
break
|
||||
} else if access == "RO" && kind == "RW" {
|
||||
break
|
||||
}
|
||||
return next(ctx)
|
||||
default:
|
||||
panic(fmt.Errorf("Unknown auth method for access check"))
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("TODO"))
|
||||
return nil, fmt.Errorf("Access denied")
|
||||
}
|
||||
|
|
|
@ -130,7 +130,7 @@ func MakeRouter(service string, conf ini.File, schema graphql.ExecutableSchema,
|
|||
requestsProcessed.Inc()
|
||||
})
|
||||
})
|
||||
router.Use(config.Middleware(conf))
|
||||
router.Use(config.Middleware(conf, service))
|
||||
router.Use(database.Middleware(db))
|
||||
router.Use(redis.Middleware(rc))
|
||||
router.Use(middleware.RealIP)
|
||||
|
|
Loading…
Reference in New Issue