@@ -0,0 +1,331 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/repositories"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"log"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var contextKey = &struct{ data string }{"Token Context Key"}
|
||||
|
||||
// ErrNoKid is returned if the key id is missing from the jwt token header,
|
||||
var ErrNoKid = errors.New("missing \"kid\" field in token")
|
||||
|
||||
// ErrKeyNotFound is returned if the key wasn't found.
|
||||
var ErrKeyNotFound = errors.New("key not found")
|
||||
|
||||
// ErrInvalidClaims is returned by parseClaims if the claims cannot be parsed
|
||||
var ErrInvalidClaims = errors.New("invalid claims in token")
|
||||
|
||||
// ErrExpired is returned by parseClaims if the expiry date is in the past
|
||||
var ErrExpired = errors.New("claims have already expired")
|
||||
|
||||
// ErrWrongUser is returned by CheckToken if the key cannot represent this user
|
||||
var ErrWrongUser = errors.New("key is not valid for this user")
|
||||
|
||||
// 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")
|
||||
|
||||
// ErrUnauthenticated is returned when the user is not authenticated
|
||||
var ErrUnauthenticated = errors.New("you are not authenticated")
|
||||
|
||||
// ErrUnauthorized is returned when the user doesn't have access to this resource
|
||||
var ErrUnauthorized = errors.New("you are not authorized to perform this action")
|
||||
|
||||
// ErrInvalidOperation is returned for operations that should never be allowed
|
||||
var ErrInvalidOperation = errors.New("no permission exists for this operation")
|
||||
|
||||
// AuthService is a service for handling the 'orizations and 'entications.
|
||||
type AuthService struct {
|
||||
keys repositories.KeyRepository
|
||||
users repositories.UserRepository
|
||||
}
|
||||
|
||||
// FindKey finds a key by id.
|
||||
func (s *AuthService) FindKey(ctx context.Context, id string) (*models.Key, error) {
|
||||
return s.keys.Find(ctx, id)
|
||||
}
|
||||
|
||||
// FindKey finds a key by id.
|
||||
func (s *AuthService) FindUser(ctx context.Context, id string) (*models.User, error) {
|
||||
return s.users.Find(ctx, id)
|
||||
}
|
||||
|
||||
// CreateKey generates a new key for the user and name. This
|
||||
// does not allow generating wildcard keys, they have to be
|
||||
// manually inserted into the DB.
|
||||
func (s *AuthService) CreateKey(ctx context.Context, name, user string) (*models.Key, error) {
|
||||
if user == "*" {
|
||||
return nil, errors.New("auth: wildcard keys not allowed")
|
||||
}
|
||||
|
||||
secret, err := s.generateSecret()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key := &models.Key{
|
||||
Name: name,
|
||||
User: user,
|
||||
Secret: secret,
|
||||
}
|
||||
|
||||
key, err = s.keys.Insert(ctx, *key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// CheckPermission does some magic.
|
||||
func (s *AuthService) CheckPermission(ctx context.Context, op string, obj interface{}) error {
|
||||
token := s.TokenFromContext(ctx)
|
||||
if token == nil {
|
||||
return ErrUnauthenticated
|
||||
}
|
||||
|
||||
if v := reflect.ValueOf(obj); v.Kind() == reflect.Struct {
|
||||
ptr := reflect.PtrTo(v.Type())
|
||||
ptrValue := reflect.New(ptr.Elem())
|
||||
ptrValue.Elem().Set(v)
|
||||
|
||||
obj = ptrValue.Interface()
|
||||
}
|
||||
|
||||
var authorized = false
|
||||
|
||||
switch v := obj.(type) {
|
||||
case *models.Channel:
|
||||
authorized = token.Permitted("channel." + op)
|
||||
case *models.Character:
|
||||
authorized = token.PermittedUser(v.Author, "member", "character."+op)
|
||||
case *models.Chapter:
|
||||
authorized = token.PermittedUser(v.Author, "member", "chapter."+op)
|
||||
case *models.Comment:
|
||||
if op == "add" && v.Author != token.UserID {
|
||||
return ErrInvalidOperation
|
||||
}
|
||||
|
||||
authorized = token.PermittedUser(v.Author, "member", "comment."+op)
|
||||
case *models.File:
|
||||
authorized = token.PermittedUser(v.Author, "member", "file."+op)
|
||||
case *models.Log:
|
||||
authorized = token.Permitted("log." + op)
|
||||
case *models.Post:
|
||||
authorized = token.Permitted("post." + op)
|
||||
case *models.Story:
|
||||
authorized = token.PermittedUser(v.Author, "member", "story."+op)
|
||||
case *models.User:
|
||||
authorized = token.Permitted("user." + op)
|
||||
default:
|
||||
log.Panicf("Invalid model %T: %#+v", v, v)
|
||||
}
|
||||
|
||||
if !authorized {
|
||||
return ErrUnauthorized
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TokenFromContext gets the token from context.
|
||||
func (s *AuthService) TokenFromContext(ctx context.Context) *models.Token {
|
||||
token, ok := ctx.Value(contextKey).(*models.Token)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
// RequestWithToken either returns the request, or the request with a new context that
|
||||
// has the token.
|
||||
func (s *AuthService) RequestWithToken(r *http.Request) *http.Request {
|
||||
header := r.Header.Get("Authorization")
|
||||
if header == "" {
|
||||
return r
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(header, "Bearer ") {
|
||||
return r
|
||||
}
|
||||
|
||||
token, err := s.CheckToken(r.Context(), header[7:])
|
||||
if err != nil {
|
||||
return r
|
||||
}
|
||||
|
||||
return r.WithContext(context.WithValue(r.Context(), contextKey, &token))
|
||||
}
|
||||
|
||||
// CheckToken reads the token string and returns a token if everything is kosher.
|
||||
func (s *AuthService) CheckToken(ctx context.Context, tokenString string) (token models.Token, err error) {
|
||||
var key *models.Key
|
||||
|
||||
jwtToken, err := jwt.Parse(tokenString, func(jwtToken *jwt.Token) (interface{}, error) {
|
||||
if _, ok := jwtToken.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", jwtToken.Header["alg"])
|
||||
}
|
||||
|
||||
kid, ok := jwtToken.Header["kid"].(string)
|
||||
if !ok {
|
||||
return nil, ErrNoKid
|
||||
}
|
||||
|
||||
key, err = s.FindKey(ctx, kid)
|
||||
if err != nil || key.ID == "" {
|
||||
return nil, ErrKeyNotFound
|
||||
}
|
||||
|
||||
return []byte(key.Secret), nil
|
||||
})
|
||||
if err != nil {
|
||||
return models.Token{}, err
|
||||
}
|
||||
|
||||
userid, permissions, err := s.parseClaims(jwtToken.Claims)
|
||||
if err != nil {
|
||||
return models.Token{}, err
|
||||
}
|
||||
|
||||
if !key.ValidForUser(userid) {
|
||||
return models.Token{}, ErrWrongUser
|
||||
}
|
||||
|
||||
user, err := s.ensureUser(ctx, userid)
|
||||
if err != nil {
|
||||
return models.Token{}, ErrDeletedUser
|
||||
}
|
||||
|
||||
acceptedPermissions := make([]string, 0, len(user.Permissions))
|
||||
if len(permissions) > 0 {
|
||||
for _, permission := range permissions {
|
||||
found := false
|
||||
|
||||
for _, userPermission := range user.Permissions {
|
||||
if permission == userPermission {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
acceptedPermissions = append(acceptedPermissions, permission)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
acceptedPermissions = append(acceptedPermissions, user.Permissions...)
|
||||
}
|
||||
|
||||
return models.Token{UserID: user.ID, Permissions: acceptedPermissions}, nil
|
||||
}
|
||||
|
||||
// AllPermissions gets all permissions and their purpose
|
||||
func (s *AuthService) AllPermissions() map[string]string {
|
||||
return map[string]string{
|
||||
"member": "Can add/edit/remove own content",
|
||||
"user.edit": "Can edit non-owned users",
|
||||
"character.add": "Can add non-owned characters",
|
||||
"character.edit": "Can edit non-owned characters",
|
||||
"character.remove": "Can remove non-owned characters",
|
||||
"channel.add": "Can add channels",
|
||||
"channel.edit": "Can edit channels",
|
||||
"channel.remove": "Can remove channels",
|
||||
"comment.edit": "Can edit non-owned comments",
|
||||
"comment.remove": "Can remove non-owned comments",
|
||||
"log.add": "Can add logs",
|
||||
"log.edit": "Can edit logs",
|
||||
"log.remove": "Can remove logs",
|
||||
"post.add": "Can add posts",
|
||||
"post.edit": "Can edit posts",
|
||||
"post.move": "Can move posts",
|
||||
"post.remove": "Can remove posts",
|
||||
"story.add": "Can add non-owned stories",
|
||||
"story.edit": "Can edit non-owned stories",
|
||||
"story.remove": "Can remove non-owned stories",
|
||||
"story.unlisted": "Can view non-owned unlisted stories",
|
||||
"file.upload": "Can upload files",
|
||||
"file.list": "Can list non-owned files",
|
||||
"file.view": "Can view non-owned files",
|
||||
"file.edit": "Can edit non-owned files",
|
||||
"file.remove": "Can remove non-owned files",
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AuthService) parseClaims(jwtClaims jwt.Claims) (userid string, permissions []string, err error) {
|
||||
mapClaims, ok := jwtClaims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
return "", nil, ErrInvalidClaims
|
||||
}
|
||||
|
||||
if !mapClaims.VerifyExpiresAt(time.Now().Unix(), true) {
|
||||
return "", nil, ErrExpired
|
||||
}
|
||||
|
||||
if userid, ok = mapClaims["user"].(string); !ok {
|
||||
return "", nil, ErrInvalidClaims
|
||||
}
|
||||
|
||||
if claimedPermissions, ok := mapClaims["permissions"].([]interface{}); ok {
|
||||
for _, permission := range claimedPermissions {
|
||||
if permission, ok := permission.(string); ok {
|
||||
permissions = append(permissions, permission)
|
||||
}
|
||||
}
|
||||
|
||||
if len(permissions) == 0 {
|
||||
return "", nil, ErrInvalidClaims
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *AuthService) generateSecret() (string, error) {
|
||||
var data [64]byte
|
||||
|
||||
_, err := rand.Read(data[:])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return base64.RawURLEncoding.EncodeToString(data[:]), nil
|
||||
}
|
||||
|
||||
func (s *AuthService) ensureUser(ctx context.Context, id string) (*models.User, error) {
|
||||
user, err := s.users.Find(ctx, id)
|
||||
if err == repositories.ErrNotFound {
|
||||
user = &models.User{
|
||||
ID: id,
|
||||
Nick: "",
|
||||
Permissions: []string{
|
||||
"member",
|
||||
"log.edit",
|
||||
"post.edit",
|
||||
"post.move",
|
||||
"post.remove",
|
||||
"file.upload",
|
||||
},
|
||||
}
|
||||
|
||||
user, err = s.users.Insert(ctx, *user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, err
|
||||
}
|
||||
+7
-6
@@ -2,7 +2,6 @@ package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/internal/notifier"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/repositories"
|
||||
@@ -12,7 +11,8 @@ import (
|
||||
)
|
||||
|
||||
type ChangeService struct {
|
||||
changes repositories.ChangeRepository
|
||||
changes repositories.ChangeRepository
|
||||
authService *AuthService
|
||||
|
||||
mutex sync.RWMutex
|
||||
buffer []models.Change
|
||||
@@ -31,7 +31,7 @@ func (s *ChangeService) List(ctx context.Context, filter models.ChangeFilter) ([
|
||||
}
|
||||
|
||||
func (s *ChangeService) Submit(ctx context.Context, model models.ChangeModel, op string, listed bool, keys []models.ChangeKey, objects ...interface{}) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
token := s.authService.TokenFromContext(ctx)
|
||||
if token == nil {
|
||||
panic("no token!")
|
||||
}
|
||||
@@ -127,15 +127,16 @@ func (s *ChangeService) Subscribe(ctx context.Context, filter models.ChangeFilte
|
||||
|
||||
func (s *ChangeService) loop() {
|
||||
for change := range s.submitQueue {
|
||||
timeout, cancel := context.WithTimeout(context.Background(), time.Second*15)
|
||||
timeout, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
|
||||
change, err := s.changes.Insert(timeout, *change)
|
||||
if err != nil {
|
||||
log.Println("Failed to insert change:")
|
||||
} else {
|
||||
log.Println("Change", change.ID, "inserted.")
|
||||
continue
|
||||
}
|
||||
|
||||
log.Println("Change", change.ID, "inserted.")
|
||||
|
||||
s.mutex.Lock()
|
||||
s.buffer = append(s.buffer, *change)
|
||||
if len(s.buffer) > 16 {
|
||||
|
||||
@@ -2,7 +2,6 @@ package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/models/changekeys"
|
||||
"git.aiterp.net/rpdata/api/repositories"
|
||||
@@ -14,6 +13,7 @@ type ChannelService struct {
|
||||
channels repositories.ChannelRepository
|
||||
loader *loaders.ChannelLoader
|
||||
changeService *ChangeService
|
||||
authService *AuthService
|
||||
}
|
||||
|
||||
func (s *ChannelService) Find(ctx context.Context, id string) (*models.Channel, error) {
|
||||
@@ -44,7 +44,7 @@ func (s *ChannelService) List(ctx context.Context, filter models.ChannelFilter)
|
||||
}
|
||||
|
||||
func (s *ChannelService) Create(ctx context.Context, name string, logged, hub bool, eventName, locationName string) (*models.Channel, error) {
|
||||
err := auth.CheckPermission(ctx, "add", &models.Channel{})
|
||||
err := s.authService.CheckPermission(ctx, "add", &models.Channel{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -91,7 +91,7 @@ func (s *ChannelService) Update(ctx context.Context, name string, update models.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = auth.CheckPermission(ctx, "edit", channel)
|
||||
err = s.authService.CheckPermission(ctx, "edit", channel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -114,7 +114,7 @@ func (s *ChannelService) Delete(ctx context.Context, name string) (*models.Chann
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = auth.CheckPermission(ctx, "remove", channel)
|
||||
err = s.authService.CheckPermission(ctx, "remove", channel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package services
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/models/changekeys"
|
||||
"git.aiterp.net/rpdata/api/repositories"
|
||||
@@ -16,6 +15,7 @@ type CharacterService struct {
|
||||
characters repositories.CharacterRepository
|
||||
loader *loaders.CharacterLoader
|
||||
changeService *ChangeService
|
||||
authService *AuthService
|
||||
}
|
||||
|
||||
// Find uses the loader to find the character by the ID.
|
||||
@@ -65,9 +65,9 @@ func (s *CharacterService) List(ctx context.Context, filter models.CharacterFilt
|
||||
}
|
||||
|
||||
func (s *CharacterService) Create(ctx context.Context, nick, name, shortName, author, description string) (*models.Character, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
token := s.authService.TokenFromContext(ctx)
|
||||
if token == nil {
|
||||
return nil, auth.ErrUnauthenticated
|
||||
return nil, ErrUnauthenticated
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
@@ -91,7 +91,7 @@ func (s *CharacterService) Create(ctx context.Context, nick, name, shortName, au
|
||||
Description: description,
|
||||
}
|
||||
|
||||
err := auth.CheckPermission(ctx, "add", character)
|
||||
err := s.authService.CheckPermission(ctx, "add", character)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -112,7 +112,7 @@ func (s *CharacterService) Update(ctx context.Context, id string, name, shortNam
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = auth.CheckPermission(ctx, "edit", character)
|
||||
err = s.authService.CheckPermission(ctx, "edit", character)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -140,7 +140,7 @@ func (s *CharacterService) AddNick(ctx context.Context, id string, nick string)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = auth.CheckPermission(ctx, "edit", character)
|
||||
err = s.authService.CheckPermission(ctx, "edit", character)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -164,7 +164,7 @@ func (s *CharacterService) RemoveNick(ctx context.Context, id string, nick strin
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = auth.CheckPermission(ctx, "edit", character)
|
||||
err = s.authService.CheckPermission(ctx, "edit", character)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -188,7 +188,7 @@ func (s *CharacterService) Delete(ctx context.Context, id string) (*models.Chara
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = auth.CheckPermission(ctx, "edit", character)
|
||||
err = s.authService.CheckPermission(ctx, "edit", character)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+6
-6
@@ -3,7 +3,6 @@ package services
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/repositories"
|
||||
"github.com/h2non/filetype"
|
||||
@@ -19,7 +18,8 @@ var ErrCouldNotUploadFile = errors.New("could not upload file")
|
||||
|
||||
// FileService is a service for files.
|
||||
type FileService struct {
|
||||
files repositories.FileRepository
|
||||
files repositories.FileRepository
|
||||
authService *AuthService
|
||||
}
|
||||
|
||||
func (s *FileService) Find(ctx context.Context, id string) (*models.File, error) {
|
||||
@@ -29,7 +29,7 @@ func (s *FileService) Find(ctx context.Context, id string) (*models.File, error)
|
||||
}
|
||||
|
||||
if !file.Public {
|
||||
err := auth.CheckPermission(ctx, "view", file)
|
||||
err := s.authService.CheckPermission(ctx, "view", file)
|
||||
if err != nil {
|
||||
return nil, repositories.ErrNotFound
|
||||
}
|
||||
@@ -39,7 +39,7 @@ func (s *FileService) Find(ctx context.Context, id string) (*models.File, error)
|
||||
}
|
||||
|
||||
func (s *FileService) List(ctx context.Context, filter models.FileFilter) ([]*models.File, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
token := s.authService.TokenFromContext(ctx)
|
||||
|
||||
if filter.Public != nil {
|
||||
if *filter.Public == false {
|
||||
@@ -48,7 +48,7 @@ func (s *FileService) List(ctx context.Context, filter models.FileFilter) ([]*mo
|
||||
}
|
||||
|
||||
if !token.PermittedUser(*filter.Author, "member", "file.list") {
|
||||
return nil, auth.ErrUnauthorized
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,7 +83,7 @@ func (s *FileService) Edit(ctx context.Context, id string, name *string, public
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = auth.CheckPermission(ctx, "edit", file)
|
||||
err = s.authService.CheckPermission(ctx, "edit", file)
|
||||
if err != nil {
|
||||
if !file.Public {
|
||||
return nil, repositories.ErrNotFound
|
||||
|
||||
+13
-14
@@ -3,10 +3,8 @@ package services
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/models/changekeys"
|
||||
"git.aiterp.net/rpdata/api/models/channels"
|
||||
"git.aiterp.net/rpdata/api/repositories"
|
||||
"git.aiterp.net/rpdata/api/services/parsers"
|
||||
"golang.org/x/sync/errgroup"
|
||||
@@ -23,6 +21,7 @@ type LogService struct {
|
||||
changeService *ChangeService
|
||||
channelService *ChannelService
|
||||
characterService *CharacterService
|
||||
authService *AuthService
|
||||
|
||||
unknownNicks map[string]int
|
||||
unknownNicksMutex sync.Mutex
|
||||
@@ -93,7 +92,7 @@ func (s *LogService) Create(ctx context.Context, title, description, channelName
|
||||
Open: open,
|
||||
}
|
||||
|
||||
if err := auth.CheckPermission(ctx, "add", log); err != nil {
|
||||
if err := s.authService.CheckPermission(ctx, "add", log); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -114,7 +113,7 @@ func (s *LogService) Create(ctx context.Context, title, description, channelName
|
||||
|
||||
// Import creates new logs from common formats.
|
||||
func (s *LogService) Import(ctx context.Context, importer models.LogImporter, date time.Time, tz *time.Location, channelName string, data string) ([]*models.Log, error) {
|
||||
if err := auth.CheckPermission(ctx, "add", &models.Log{}); err != nil {
|
||||
if err := s.authService.CheckPermission(ctx, "add", &models.Log{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -126,7 +125,7 @@ func (s *LogService) Import(ctx context.Context, importer models.LogImporter, da
|
||||
}
|
||||
|
||||
eventName := ""
|
||||
if channel, err := channels.FindName(channelName); err == nil {
|
||||
if channel, err := s.channelService.Find(ctx, channelName); err == nil {
|
||||
eventName = channel.EventName
|
||||
}
|
||||
|
||||
@@ -212,7 +211,7 @@ func (s *LogService) Update(ctx context.Context, id string, update models.LogUpd
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := auth.CheckPermission(ctx, "edit", log); err != nil {
|
||||
if err := s.authService.CheckPermission(ctx, "edit", log); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -232,7 +231,7 @@ func (s *LogService) SplitLog(ctx context.Context, logId string, startPostId str
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := auth.CheckPermission(ctx, "add", l); err != nil {
|
||||
if err := s.authService.CheckPermission(ctx, "add", l); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -314,11 +313,11 @@ func (s *LogService) SplitLog(ctx context.Context, logId string, startPostId str
|
||||
|
||||
func (s *LogService) MergeLogs(ctx context.Context, targetID string, sourceID string, removeAfter bool) (*models.Log, error) {
|
||||
// Check permissions
|
||||
if err := auth.CheckPermission(ctx, "edit", &models.Log{}); err != nil {
|
||||
if err := s.authService.CheckPermission(ctx, "edit", &models.Log{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if removeAfter {
|
||||
if err := auth.CheckPermission(ctx, "remove", &models.Log{}); err != nil {
|
||||
if err := s.authService.CheckPermission(ctx, "remove", &models.Log{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -393,7 +392,7 @@ func (s *LogService) AddPost(ctx context.Context, logId string, time time.Time,
|
||||
Time: time,
|
||||
}
|
||||
|
||||
if err := auth.CheckPermission(ctx, "add", post); err != nil {
|
||||
if err := s.authService.CheckPermission(ctx, "add", post); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -424,7 +423,7 @@ func (s *LogService) EditPost(ctx context.Context, id string, update models.Post
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := auth.CheckPermission(ctx, "edit", post); err != nil {
|
||||
if err := s.authService.CheckPermission(ctx, "edit", post); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -460,7 +459,7 @@ func (s *LogService) MovePost(ctx context.Context, id string, position int) ([]*
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := auth.CheckPermission(ctx, "move", post); err != nil {
|
||||
if err := s.authService.CheckPermission(ctx, "move", post); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -491,7 +490,7 @@ func (s *LogService) DeletePost(ctx context.Context, id string) (*models.Post, e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := auth.CheckPermission(ctx, "remove", post); err != nil {
|
||||
if err := s.authService.CheckPermission(ctx, "remove", post); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -523,7 +522,7 @@ func (s *LogService) Delete(ctx context.Context, id string) (*models.Log, error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := auth.CheckPermission(ctx, "remove", log); err != nil {
|
||||
if err := s.authService.CheckPermission(ctx, "remove", log); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
+16
-1
@@ -13,24 +13,37 @@ type Bundle struct {
|
||||
Logs *LogService
|
||||
Channels *ChannelService
|
||||
Stories *StoryService
|
||||
Auth *AuthService
|
||||
Files *FileService
|
||||
}
|
||||
|
||||
// NewBundle creates a new bundle.
|
||||
func NewBundle(db database.Database) *Bundle {
|
||||
bundle := &Bundle{}
|
||||
bundle.Auth = &AuthService{
|
||||
keys: db.Keys(),
|
||||
users: db.Users(),
|
||||
}
|
||||
bundle.Files = &FileService{
|
||||
files: nil,
|
||||
authService: bundle.Auth,
|
||||
}
|
||||
bundle.Changes = &ChangeService{
|
||||
changes: db.Changes(),
|
||||
changes: db.Changes(),
|
||||
authService: bundle.Auth,
|
||||
}
|
||||
bundle.Tags = &TagService{tags: db.Tags()}
|
||||
bundle.Characters = &CharacterService{
|
||||
characters: db.Characters(),
|
||||
loader: loaders.CharacterLoaderFromRepository(db.Characters()),
|
||||
changeService: bundle.Changes,
|
||||
authService: bundle.Auth,
|
||||
}
|
||||
bundle.Channels = &ChannelService{
|
||||
channels: db.Channels(),
|
||||
loader: loaders.ChannelLoaderFromRepository(db.Channels()),
|
||||
changeService: bundle.Changes,
|
||||
authService: bundle.Auth,
|
||||
}
|
||||
bundle.Logs = &LogService{
|
||||
logs: db.Logs(),
|
||||
@@ -38,6 +51,7 @@ func NewBundle(db database.Database) *Bundle {
|
||||
changeService: bundle.Changes,
|
||||
channelService: bundle.Channels,
|
||||
characterService: bundle.Characters,
|
||||
authService: bundle.Auth,
|
||||
|
||||
unknownNicks: make(map[string]int, 512),
|
||||
}
|
||||
@@ -47,6 +61,7 @@ func NewBundle(db database.Database) *Bundle {
|
||||
comments: db.Comments(),
|
||||
changeService: bundle.Changes,
|
||||
characterService: bundle.Characters,
|
||||
authService: bundle.Auth,
|
||||
}
|
||||
|
||||
return bundle
|
||||
|
||||
+25
-25
@@ -3,7 +3,6 @@ package services
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/internal/generate"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/models/changekeys"
|
||||
@@ -18,6 +17,7 @@ type StoryService struct {
|
||||
comments repositories.CommentRepository
|
||||
changeService *ChangeService
|
||||
characterService *CharacterService
|
||||
authService *AuthService
|
||||
}
|
||||
|
||||
func (s *StoryService) FindStory(ctx context.Context, id string) (*models.Story, error) {
|
||||
@@ -46,9 +46,9 @@ func (s *StoryService) ListComments(ctx context.Context, chapter models.Chapter,
|
||||
|
||||
func (s *StoryService) CreateStory(ctx context.Context, name string, author *string, category models.StoryCategory, listed, open bool, tags []models.Tag, createdDate, fictionalDate time.Time) (*models.Story, error) {
|
||||
if author == nil {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
token := s.authService.TokenFromContext(ctx)
|
||||
if token == nil {
|
||||
return nil, auth.ErrUnauthenticated
|
||||
return nil, ErrUnauthenticated
|
||||
}
|
||||
|
||||
author = &token.UserID
|
||||
@@ -66,7 +66,7 @@ func (s *StoryService) CreateStory(ctx context.Context, name string, author *str
|
||||
UpdatedDate: createdDate,
|
||||
}
|
||||
|
||||
if err := auth.CheckPermission(ctx, "add", story); err != nil {
|
||||
if err := s.authService.CheckPermission(ctx, "add", story); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -82,9 +82,9 @@ func (s *StoryService) CreateStory(ctx context.Context, name string, author *str
|
||||
|
||||
func (s *StoryService) CreateChapter(ctx context.Context, story models.Story, title, source string, author *string, createdDate time.Time, fictionalDate *time.Time, commentMode models.ChapterCommentMode) (*models.Chapter, error) {
|
||||
if author == nil {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
token := s.authService.TokenFromContext(ctx)
|
||||
if token == nil {
|
||||
return nil, auth.ErrUnauthenticated
|
||||
return nil, ErrUnauthenticated
|
||||
}
|
||||
|
||||
author = &token.UserID
|
||||
@@ -106,11 +106,11 @@ func (s *StoryService) CreateChapter(ctx context.Context, story models.Story, ti
|
||||
}
|
||||
|
||||
if story.Open {
|
||||
if !auth.TokenFromContext(ctx).Permitted("member", "chapter.add") {
|
||||
return nil, auth.ErrUnauthorized
|
||||
if !s.authService.TokenFromContext(ctx).Permitted("member", "chapter.add") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
} else {
|
||||
if err := auth.CheckPermission(ctx, "add", chapter); err != nil {
|
||||
if err := s.authService.CheckPermission(ctx, "add", chapter); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -143,10 +143,10 @@ func (s *StoryService) CreateComment(ctx context.Context, chapter models.Chapter
|
||||
}
|
||||
|
||||
if author == "" {
|
||||
if token := auth.TokenFromContext(ctx); token != nil {
|
||||
if token := s.authService.TokenFromContext(ctx); token != nil {
|
||||
author = token.UserID
|
||||
} else {
|
||||
return nil, auth.ErrUnauthenticated
|
||||
return nil, ErrUnauthenticated
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ func (s *StoryService) CreateComment(ctx context.Context, chapter models.Chapter
|
||||
EditedDate: createdDate,
|
||||
Source: source,
|
||||
}
|
||||
if err := auth.CheckPermission(ctx, "add", comment); err != nil {
|
||||
if err := s.authService.CheckPermission(ctx, "add", comment); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ func (s *StoryService) EditStory(ctx context.Context, story *models.Story, name
|
||||
panic("StoryService.Edit called with nil story")
|
||||
}
|
||||
|
||||
if err := auth.CheckPermission(ctx, "edit", story); err != nil {
|
||||
if err := s.authService.CheckPermission(ctx, "edit", story); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ func (s *StoryService) EditStory(ctx context.Context, story *models.Story, name
|
||||
}
|
||||
|
||||
func (s *StoryService) AddStoryTag(ctx context.Context, story models.Story, tag models.Tag) (*models.Story, error) {
|
||||
if err := auth.CheckPermission(ctx, "edit", &story); err != nil {
|
||||
if err := s.authService.CheckPermission(ctx, "edit", &story); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ func (s *StoryService) AddStoryTag(ctx context.Context, story models.Story, tag
|
||||
}
|
||||
|
||||
func (s *StoryService) RemoveStoryTag(ctx context.Context, story models.Story, tag models.Tag) (*models.Story, error) {
|
||||
if err := auth.CheckPermission(ctx, "edit", &story); err != nil {
|
||||
if err := s.authService.CheckPermission(ctx, "edit", &story); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -249,7 +249,7 @@ func (s *StoryService) EditChapter(ctx context.Context, chapter *models.Chapter,
|
||||
panic("StoryService.EditChapter called with nil chapter")
|
||||
}
|
||||
|
||||
if err := auth.CheckPermission(ctx, "edit", chapter); err != nil {
|
||||
if err := s.authService.CheckPermission(ctx, "edit", chapter); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -274,16 +274,16 @@ func (s *StoryService) EditChapter(ctx context.Context, chapter *models.Chapter,
|
||||
}
|
||||
|
||||
func (s *StoryService) MoveChapter(ctx context.Context, chapter *models.Chapter, from, to models.Story) (*models.Chapter, error) {
|
||||
if err := auth.CheckPermission(ctx, "move", chapter); err != nil {
|
||||
if err := s.authService.CheckPermission(ctx, "move", chapter); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if to.Open {
|
||||
if !auth.TokenFromContext(ctx).Permitted("member", "chapter.add") {
|
||||
return nil, auth.ErrUnauthorized
|
||||
if !s.authService.TokenFromContext(ctx).Permitted("member", "chapter.add") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
} else {
|
||||
if err := auth.CheckPermission(ctx, "add", chapter); err != nil {
|
||||
if err := s.authService.CheckPermission(ctx, "add", chapter); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -304,7 +304,7 @@ func (s *StoryService) EditComment(ctx context.Context, comment *models.Comment,
|
||||
panic("StoryService.EditChapter called with nil chapter")
|
||||
}
|
||||
|
||||
if err := auth.CheckPermission(ctx, "edit", comment); err != nil {
|
||||
if err := s.authService.CheckPermission(ctx, "edit", comment); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -343,7 +343,7 @@ func (s *StoryService) EditComment(ctx context.Context, comment *models.Comment,
|
||||
}
|
||||
|
||||
func (s *StoryService) RemoveStory(ctx context.Context, story *models.Story) error {
|
||||
if err := auth.CheckPermission(ctx, "add", story); err != nil {
|
||||
if err := s.authService.CheckPermission(ctx, "add", story); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -358,7 +358,7 @@ func (s *StoryService) RemoveStory(ctx context.Context, story *models.Story) err
|
||||
}
|
||||
|
||||
func (s *StoryService) RemoveChapter(ctx context.Context, chapter *models.Chapter) error {
|
||||
if err := auth.CheckPermission(ctx, "remove", chapter); err != nil {
|
||||
if err := s.authService.CheckPermission(ctx, "remove", chapter); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -377,7 +377,7 @@ func (s *StoryService) RemoveChapter(ctx context.Context, chapter *models.Chapte
|
||||
}
|
||||
|
||||
func (s *StoryService) RemoveComment(ctx context.Context, comment *models.Comment) error {
|
||||
if err := auth.CheckPermission(ctx, "remove", comment); err != nil {
|
||||
if err := s.authService.CheckPermission(ctx, "remove", comment); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -409,7 +409,7 @@ func (s *StoryService) permittedCharacter(ctx context.Context, permissionKind, c
|
||||
return errors.New("character could not be found")
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
token := s.authService.TokenFromContext(ctx)
|
||||
if character.Author != token.UserID && !token.Permitted(permissionKind+".edit") {
|
||||
return errors.New("you are not permitted to use others' character")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user