auth: Moved token and user to models package, added graphql endpoint to check token
the build failed
the build failed
This commit is contained in:
@@ -49,5 +49,9 @@ models:
|
|||||||
model: git.aiterp.net/rpdata/api/models.File
|
model: git.aiterp.net/rpdata/api/models.File
|
||||||
FilesFilter:
|
FilesFilter:
|
||||||
model: git.aiterp.net/rpdata/api/models/files.Filter
|
model: git.aiterp.net/rpdata/api/models/files.Filter
|
||||||
|
Token:
|
||||||
|
model: git.aiterp.net/rpdata/api/models.Token
|
||||||
|
User:
|
||||||
|
model: git.aiterp.net/rpdata/api/models.User
|
||||||
Date:
|
Date:
|
||||||
model: git.aiterp.net/rpdata/api/models/scalars.Date
|
model: git.aiterp.net/rpdata/api/models/scalars.Date
|
||||||
@@ -37,3 +37,7 @@ func (r *rootResolver) Story() StoryResolver {
|
|||||||
func (r *rootResolver) File() FileResolver {
|
func (r *rootResolver) File() FileResolver {
|
||||||
return &types.FileResolver
|
return &types.FileResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *rootResolver) Token() TokenResolver {
|
||||||
|
return &types.TokenResolver
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package queries
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"git.aiterp.net/rpdata/api/internal/auth"
|
||||||
|
"git.aiterp.net/rpdata/api/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *resolver) Token(ctx context.Context) (models.Token, error) {
|
||||||
|
token := auth.TokenFromContext(ctx)
|
||||||
|
if !token.Authenticated() {
|
||||||
|
return models.Token{}, errors.New("No (valid) token")
|
||||||
|
}
|
||||||
|
|
||||||
|
return *token, nil
|
||||||
|
}
|
||||||
@@ -51,6 +51,10 @@ type Query {
|
|||||||
|
|
||||||
# Find files
|
# Find files
|
||||||
files(filter: FilesFilter): [File!]!
|
files(filter: FilesFilter): [File!]!
|
||||||
|
|
||||||
|
|
||||||
|
# Get information about the token, useful for debugging.
|
||||||
|
token: Token!
|
||||||
}
|
}
|
||||||
|
|
||||||
# A Date represents a RFC3339 encoded date with up to millisecond precision.
|
# A Date represents a RFC3339 encoded date with up to millisecond precision.
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Token represents the data parsed and validated from the bearer token.
|
||||||
|
type Token {
|
||||||
|
# The user represented in the token.
|
||||||
|
user: User!
|
||||||
|
|
||||||
|
# The permissions for the token.
|
||||||
|
permissions: [String!]!
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# A user represents a member on the RPData platform.
|
||||||
|
type User {
|
||||||
|
# The user's unique ID / username
|
||||||
|
id: String!
|
||||||
|
|
||||||
|
# What the user is permitted to do.
|
||||||
|
permissions: [String!]!
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.aiterp.net/rpdata/api/models"
|
||||||
|
"git.aiterp.net/rpdata/api/models/users"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tokenResolver struct{}
|
||||||
|
|
||||||
|
func (r *tokenResolver) User(ctx context.Context, token *models.Token) (models.User, error) {
|
||||||
|
return users.Find(token.UserID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenResolver is a resolver
|
||||||
|
var TokenResolver tokenResolver
|
||||||
+12
-57
@@ -8,6 +8,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.aiterp.net/rpdata/api/models"
|
||||||
|
"git.aiterp.net/rpdata/api/models/users"
|
||||||
jwt "github.com/dgrijalva/jwt-go"
|
jwt "github.com/dgrijalva/jwt-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -34,56 +36,9 @@ var ErrWrongPermissions = errors.New("User does not have these permissions")
|
|||||||
// ErrDeletedUser is returned by CheckToken if the key can represent this user, but the user doesn't exist.
|
// ErrDeletedUser is returned by CheckToken if the key can represent this user, but the user doesn't exist.
|
||||||
var ErrDeletedUser = errors.New("User was not found")
|
var ErrDeletedUser = errors.New("User was not found")
|
||||||
|
|
||||||
// A Token contains the parsed results from an bearer token. Its methods are safe to use with a nil receiver, but
|
|
||||||
// the userID should be checked.
|
|
||||||
type Token struct {
|
|
||||||
UserID string
|
|
||||||
Permissions []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authenticated returns true if the token is non-nil and parsed
|
|
||||||
func (token *Token) Authenticated() bool {
|
|
||||||
return token != nil && token.UserID != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Permitted returns true if the token is non-nil and has the given permission or the "admin" permission
|
|
||||||
func (token *Token) Permitted(permissions ...string) bool {
|
|
||||||
if token == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tokenPermission := range token.Permissions {
|
|
||||||
if tokenPermission == "admin" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, permission := range permissions {
|
|
||||||
if permission == tokenPermission {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// PermittedUser checks the first permission if the user matches, the second otherwise. This is a common
|
|
||||||
// pattern.
|
|
||||||
func (token *Token) PermittedUser(userID, permissionIfUser, permissionOtherwise string) bool {
|
|
||||||
if token == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if token.UserID == userID {
|
|
||||||
return token.Permitted(permissionIfUser)
|
|
||||||
}
|
|
||||||
|
|
||||||
return token.Permitted(permissionOtherwise)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TokenFromContext gets the token from context.
|
// TokenFromContext gets the token from context.
|
||||||
func TokenFromContext(ctx context.Context) *Token {
|
func TokenFromContext(ctx context.Context) *models.Token {
|
||||||
token, ok := ctx.Value(contextKey).(*Token)
|
token, ok := ctx.Value(contextKey).(*models.Token)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -112,7 +67,7 @@ func RequestWithToken(r *http.Request) *http.Request {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CheckToken reads the token string and returns a token if everything is kosher.
|
// CheckToken reads the token string and returns a token if everything is kosher.
|
||||||
func CheckToken(tokenString string) (token Token, err error) {
|
func CheckToken(tokenString string) (token models.Token, err error) {
|
||||||
var key Key
|
var key Key
|
||||||
|
|
||||||
jwtToken, err := jwt.Parse(tokenString, func(jwtToken *jwt.Token) (interface{}, error) {
|
jwtToken, err := jwt.Parse(tokenString, func(jwtToken *jwt.Token) (interface{}, error) {
|
||||||
@@ -133,21 +88,21 @@ func CheckToken(tokenString string) (token Token, err error) {
|
|||||||
return []byte(key.Secret), nil
|
return []byte(key.Secret), nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Token{}, err
|
return models.Token{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
userid, permissions, err := parseClaims(jwtToken.Claims)
|
userid, permissions, err := parseClaims(jwtToken.Claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Token{}, err
|
return models.Token{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !key.ValidForUser(userid) {
|
if !key.ValidForUser(userid) {
|
||||||
return Token{}, ErrWrongUser
|
return models.Token{}, ErrWrongUser
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := FindUser(userid)
|
user, err := users.Ensure(userid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Token{}, ErrDeletedUser
|
return models.Token{}, ErrDeletedUser
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, permission := range permissions {
|
for _, permission := range permissions {
|
||||||
@@ -161,11 +116,11 @@ func CheckToken(tokenString string) (token Token, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
return Token{}, ErrWrongPermissions
|
return models.Token{}, ErrWrongPermissions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Token{UserID: token.UserID, Permissions: permissions}, nil
|
return models.Token{UserID: user.ID, Permissions: permissions}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseClaims(jwtClaims jwt.Claims) (userid string, permissions []string, err error) {
|
func parseClaims(jwtClaims jwt.Claims) (userid string, permissions []string, err error) {
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
package auth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.aiterp.net/rpdata/api/internal/store"
|
|
||||||
"github.com/globalsign/mgo"
|
|
||||||
)
|
|
||||||
|
|
||||||
var userCollection *mgo.Collection
|
|
||||||
|
|
||||||
// A User represents user information about a user that has logged in.
|
|
||||||
type User struct {
|
|
||||||
ID string `bson:"_id" json:"id"`
|
|
||||||
Nick string `bson:"nick,omitempty" json:"nick,omitempty"`
|
|
||||||
Permissions []string `bson:"permissions" json:"permissions"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Permitted returns true if either of the permissions can be found
|
|
||||||
//
|
|
||||||
// `token.UserID == page.Author || token.Permitted("story.edit")`
|
|
||||||
func (user *User) Permitted(permissions ...string) bool {
|
|
||||||
for i := range permissions {
|
|
||||||
for j := range user.Permissions {
|
|
||||||
if permissions[i] == user.Permissions[j] {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindUser finds a user by userid
|
|
||||||
func FindUser(userid string) (User, error) {
|
|
||||||
user := User{}
|
|
||||||
err := userCollection.FindId(userid).One(&user)
|
|
||||||
|
|
||||||
if err == mgo.ErrNotFound {
|
|
||||||
user := User{
|
|
||||||
ID: userid,
|
|
||||||
Nick: "",
|
|
||||||
Permissions: []string{
|
|
||||||
"member",
|
|
||||||
"log.edit",
|
|
||||||
"post.edit",
|
|
||||||
"post.move",
|
|
||||||
"file.upload",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
err := userCollection.Insert(user)
|
|
||||||
if err != nil {
|
|
||||||
return User{}, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return user, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
store.HandleInit(func(db *mgo.Database) {
|
|
||||||
userCollection = db.C("core.users")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
// A Token contains the parsed results from an bearer token. Its methods are safe to use with a nil receiver, but
|
||||||
|
// the userID should be checked.
|
||||||
|
type Token struct {
|
||||||
|
UserID string
|
||||||
|
Permissions []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticated returns true if the token is non-nil and parsed
|
||||||
|
func (token *Token) Authenticated() bool {
|
||||||
|
return token != nil && token.UserID != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permitted returns true if the token is non-nil and has the given permission or the "admin" permission
|
||||||
|
func (token *Token) Permitted(permissions ...string) bool {
|
||||||
|
if token == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tokenPermission := range token.Permissions {
|
||||||
|
if tokenPermission == "admin" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, permission := range permissions {
|
||||||
|
if permission == tokenPermission {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// PermittedUser checks the first permission if the user matches, the second otherwise. This is a common
|
||||||
|
// pattern.
|
||||||
|
func (token *Token) PermittedUser(userID, permissionIfUser, permissionOtherwise string) bool {
|
||||||
|
if token == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if token.UserID == userID {
|
||||||
|
return token.Permitted(permissionIfUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
return token.Permitted(permissionOtherwise)
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
// A User represents user information about a user that has logged in.
|
||||||
|
type User struct {
|
||||||
|
ID string `bson:"_id" json:"id"`
|
||||||
|
Nick string `bson:"nick,omitempty" json:"nick,omitempty"`
|
||||||
|
Permissions []string `bson:"permissions" json:"permissions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permitted returns true if either of the permissions can be found
|
||||||
|
//
|
||||||
|
// `token.UserID == page.Author || token.Permitted("story.edit")`
|
||||||
|
func (user *User) Permitted(permissions ...string) bool {
|
||||||
|
for i := range permissions {
|
||||||
|
for j := range user.Permissions {
|
||||||
|
if permissions[i] == user.Permissions[j] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package users
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.aiterp.net/rpdata/api/internal/store"
|
||||||
|
"github.com/globalsign/mgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
var collection *mgo.Collection
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
store.HandleInit(func(db *mgo.Database) {
|
||||||
|
collection = db.C("core.users")
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package users
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.aiterp.net/rpdata/api/models"
|
||||||
|
"github.com/globalsign/mgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ensure finds a user by id, or makes a new one.
|
||||||
|
func Ensure(id string) (models.User, error) {
|
||||||
|
user := models.User{}
|
||||||
|
err := collection.FindId(id).One(&user)
|
||||||
|
|
||||||
|
if err == mgo.ErrNotFound {
|
||||||
|
user = models.User{
|
||||||
|
ID: id,
|
||||||
|
Nick: "",
|
||||||
|
Permissions: []string{
|
||||||
|
"member",
|
||||||
|
"log.edit",
|
||||||
|
"post.edit",
|
||||||
|
"post.move",
|
||||||
|
"file.upload",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := collection.Insert(user)
|
||||||
|
if err != nil {
|
||||||
|
return models.User{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package users
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.aiterp.net/rpdata/api/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Find finds a user by id
|
||||||
|
func Find(id string) (models.User, error) {
|
||||||
|
user := models.User{}
|
||||||
|
err := collection.FindId(id).One(&user)
|
||||||
|
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user