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
|
||||
FilesFilter:
|
||||
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:
|
||||
model: git.aiterp.net/rpdata/api/models/scalars.Date
|
||||
@@ -37,3 +37,7 @@ func (r *rootResolver) Story() StoryResolver {
|
||||
func (r *rootResolver) File() 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
|
||||
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.
|
||||
|
||||
@@ -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"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/models/users"
|
||||
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.
|
||||
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.
|
||||
func TokenFromContext(ctx context.Context) *Token {
|
||||
token, ok := ctx.Value(contextKey).(*Token)
|
||||
func TokenFromContext(ctx context.Context) *models.Token {
|
||||
token, ok := ctx.Value(contextKey).(*models.Token)
|
||||
if !ok {
|
||||
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.
|
||||
func CheckToken(tokenString string) (token Token, err error) {
|
||||
func CheckToken(tokenString string) (token models.Token, err error) {
|
||||
var key Key
|
||||
|
||||
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
|
||||
})
|
||||
if err != nil {
|
||||
return Token{}, err
|
||||
return models.Token{}, err
|
||||
}
|
||||
|
||||
userid, permissions, err := parseClaims(jwtToken.Claims)
|
||||
if err != nil {
|
||||
return Token{}, err
|
||||
return models.Token{}, err
|
||||
}
|
||||
|
||||
if !key.ValidForUser(userid) {
|
||||
return Token{}, ErrWrongUser
|
||||
return models.Token{}, ErrWrongUser
|
||||
}
|
||||
|
||||
user, err := FindUser(userid)
|
||||
user, err := users.Ensure(userid)
|
||||
if err != nil {
|
||||
return Token{}, ErrDeletedUser
|
||||
return models.Token{}, ErrDeletedUser
|
||||
}
|
||||
|
||||
for _, permission := range permissions {
|
||||
@@ -161,11 +116,11 @@ func CheckToken(tokenString string) (token Token, err error) {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@@ -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