Browse Source

auth: Replaced old system with api key-based system.

1.0
Gisle Aune 6 years ago
parent
commit
dac0060155
  1. 16
      cmd/rpdata-graphiql/main.go
  2. 8
      graphql/resolver/mutations/addChannel.go
  3. 12
      graphql/resolver/mutations/addChapter.go
  4. 12
      graphql/resolver/mutations/addCharacter.go
  5. 10
      graphql/resolver/mutations/addCharacterNick.go
  6. 8
      graphql/resolver/mutations/addLog.go
  7. 8
      graphql/resolver/mutations/addPost.go
  8. 12
      graphql/resolver/mutations/addStory.go
  9. 10
      graphql/resolver/mutations/addStoryTag.go
  10. 8
      graphql/resolver/mutations/editChannel.go
  11. 10
      graphql/resolver/mutations/editChapter.go
  12. 10
      graphql/resolver/mutations/editCharacter.go
  13. 8
      graphql/resolver/mutations/editFile.go
  14. 8
      graphql/resolver/mutations/editLog.go
  15. 8
      graphql/resolver/mutations/editPost.go
  16. 10
      graphql/resolver/mutations/editStory.go
  17. 8
      graphql/resolver/mutations/movePost.go
  18. 8
      graphql/resolver/mutations/removeChannel.go
  19. 8
      graphql/resolver/mutations/removeChapter.go
  20. 10
      graphql/resolver/mutations/removeCharacter.go
  21. 10
      graphql/resolver/mutations/removeCharacterNick.go
  22. 8
      graphql/resolver/mutations/removeFile.go
  23. 8
      graphql/resolver/mutations/removeLog.go
  24. 8
      graphql/resolver/mutations/removePost.go
  25. 10
      graphql/resolver/mutations/removeStory.go
  26. 10
      graphql/resolver/mutations/removeStoryTag.go
  27. 8
      graphql/resolver/queries/files.go
  28. 11
      graphql/resolver/queries/stories.go
  29. 16
      graphql/resolver/types/session.go
  30. 16
      graphql/resolver/types/user.go
  31. 109
      internal/auth/key.go
  32. 198
      internal/auth/token.go
  33. 4
      internal/auth/user.go
  34. 20
      internal/session/context.go
  35. 40
      internal/session/defaults.go
  36. 182
      internal/session/session.go

16
cmd/rpdata-graphiql/main.go

@ -9,7 +9,7 @@ import (
"git.aiterp.net/rpdata/api/graphql/loader"
"git.aiterp.net/rpdata/api/graphql/resolver"
"git.aiterp.net/rpdata/api/graphql/schema"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/internal/store"
"git.aiterp.net/rpdata/api/model/change"
"git.aiterp.net/rpdata/api/model/file"
@ -46,19 +46,19 @@ func main() {
relayHandler := &relay.Handler{Schema: schema}
http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) {
r = session.Load(w, r)
l := loader.New()
r = r.WithContext(l.ToContext(r.Context()))
r = auth.RequestWithToken(r)
relayHandler.ServeHTTP(w, r)
})
http.HandleFunc("/upload", func(w http.ResponseWriter, r *http.Request) {
r = session.Load(w, r)
sess := session.FromContext(r.Context())
user := sess.User()
if user == nil || !user.Permitted("file.upload") {
r = auth.RequestWithToken(r)
token := auth.TokenFromContext(r.Context())
if !token.Permitted("file.upload") {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
@ -74,7 +74,7 @@ func main() {
return
}
file, err := file.Upload(r.Context(), header.Filename, header.Header.Get("Content-Type"), user.ID, header.Size, formFile)
file, err := file.Upload(r.Context(), header.Filename, header.Header.Get("Content-Type"), token.UserID, header.Size, formFile)
if err != nil {
http.Error(w, "Internal (2): "+err.Error(), http.StatusInternalServerError)
return

8
graphql/resolver/mutations/addChannel.go

@ -4,7 +4,7 @@ import (
"context"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/change"
"git.aiterp.net/rpdata/api/model/channel"
)
@ -24,8 +24,8 @@ type ChannelAddArgs struct {
func (r *MutationResolver) AddChannel(ctx context.Context, args *ChannelAddArgs) (*types.ChannelResolver, error) {
input := args.Input
user := session.FromContext(ctx).User()
if user == nil || !user.Permitted("channel.add") {
token := auth.TokenFromContext(ctx)
if !token.Permitted("channel.add") {
return nil, ErrUnauthorized
}
@ -48,7 +48,7 @@ func (r *MutationResolver) AddChannel(ctx context.Context, args *ChannelAddArgs)
return nil, err
}
go change.Submit("Channel", "add", user.ID, channel.Name, map[string]interface{}{
go change.Submit("Channel", "add", token.UserID, channel.Name, map[string]interface{}{
"logged": channel.Logged,
"hub": channel.Hub,
"location": input.LocationName,

12
graphql/resolver/mutations/addChapter.go

@ -5,7 +5,7 @@ import (
"time"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/change"
"git.aiterp.net/rpdata/api/model/story"
)
@ -25,8 +25,8 @@ type AddChapterArgs struct {
func (r *MutationResolver) AddChapter(ctx context.Context, args *AddChapterArgs) (*types.ChapterResolver, error) {
input := args.Input
user := session.FromContext(ctx).User()
if user == nil || !user.Permitted("member", "chapter.add") {
token := auth.TokenFromContext(ctx)
if !token.Permitted("member", "chapter.add") {
return nil, ErrUnauthorized
}
@ -35,11 +35,11 @@ func (r *MutationResolver) AddChapter(ctx context.Context, args *AddChapterArgs)
return nil, err
}
author := user.ID
author := token.UserID
if input.Author != nil {
author = *input.Author
if user.ID != author && !user.Permitted("chapter.add") {
if token.UserID != author && !token.Permitted("chapter.add") {
return nil, ErrPermissionDenied
}
}
@ -57,7 +57,7 @@ func (r *MutationResolver) AddChapter(ctx context.Context, args *AddChapterArgs)
return nil, err
}
go change.Submit("Chapter", "add", user.ID, chapter.ID, map[string]interface{}{
go change.Submit("Chapter", "add", token.UserID, chapter.ID, map[string]interface{}{
"title": input.Title,
"author": author,
"fictionalDate": fictionalDate,

12
graphql/resolver/mutations/addCharacter.go

@ -5,7 +5,7 @@ import (
"strings"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/change"
"git.aiterp.net/rpdata/api/model/character"
"git.aiterp.net/rpdata/api/model/log"
@ -23,8 +23,8 @@ type AddCharacterInput struct {
// AddCharacter resolves the addCharacter mutation
func (r *MutationResolver) AddCharacter(ctx context.Context, args struct{ Input *AddCharacterInput }) (*types.CharacterResolver, error) {
input := args.Input
user := session.FromContext(ctx).User()
if user == nil || !user.Permitted("member", "character.add") {
token := auth.TokenFromContext(ctx)
if !token.Permitted("member", "character.add") {
return nil, ErrUnauthorized
}
@ -39,11 +39,11 @@ func (r *MutationResolver) AddCharacter(ctx context.Context, args struct{ Input
shortName = strings.SplitN(input.Name, " ", 2)[0]
}
author := user.ID
author := token.UserID
if input.Author != nil {
author = *input.Author
if author != user.ID && !user.Permitted("character.add") {
if author != token.UserID && !token.Permitted("character.add") {
return nil, ErrPermissionDenied
}
}
@ -58,7 +58,7 @@ func (r *MutationResolver) AddCharacter(ctx context.Context, args struct{ Input
return nil, err
}
go change.Submit("Character", "add", user.ID, character.ID, map[string]interface{}{
go change.Submit("Character", "add", token.UserID, character.ID, map[string]interface{}{
"name": character.Name,
"nick": character.Nicks[0],
"author": character.Author,

10
graphql/resolver/mutations/addCharacterNick.go

@ -4,7 +4,7 @@ import (
"context"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/change"
"git.aiterp.net/rpdata/api/model/character"
"git.aiterp.net/rpdata/api/model/log"
@ -20,8 +20,8 @@ type AddCharacterNickInput struct {
func (r *MutationResolver) AddCharacterNick(ctx context.Context, args struct{ Input *AddCharacterNickInput }) (*types.CharacterResolver, error) {
input := args.Input
user := session.FromContext(ctx).User()
if user == nil || !user.Permitted("member") {
token := auth.TokenFromContext(ctx)
if !token.Permitted("member") {
return nil, ErrUnauthorized
}
@ -29,7 +29,7 @@ func (r *MutationResolver) AddCharacterNick(ctx context.Context, args struct{ In
if err != nil {
return nil, err
}
if character.Author != user.ID && !user.Permitted("character.edit") {
if character.Author != token.UserID && !token.Permitted("character.edit") {
return nil, ErrPermissionDenied
}
@ -38,7 +38,7 @@ func (r *MutationResolver) AddCharacterNick(ctx context.Context, args struct{ In
return nil, err
}
go change.Submit("Character", "add.nick", user.ID, character.ID, map[string]interface{}{
go change.Submit("Character", "add.nick", token.UserID, character.ID, map[string]interface{}{
"nick": input.Nick,
})

8
graphql/resolver/mutations/addLog.go

@ -5,7 +5,7 @@ import (
"time"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/change"
"git.aiterp.net/rpdata/api/model/log"
)
@ -26,8 +26,8 @@ type LogAddArgs struct {
func (r *MutationResolver) AddLog(ctx context.Context, args *LogAddArgs) (*types.LogResolver, error) {
input := args.Input
user := session.FromContext(ctx).User()
if user == nil || !user.Permitted("log.add") {
token := auth.TokenFromContext(ctx)
if !token.Permitted("log.add") {
return nil, ErrUnauthorized
}
@ -58,7 +58,7 @@ func (r *MutationResolver) AddLog(ctx context.Context, args *LogAddArgs) (*types
return nil, err
}
go change.Submit("Log", "add", user.ID, log.ID, map[string]interface{}{
go change.Submit("Log", "add", token.UserID, log.ID, map[string]interface{}{
"channel": log.Channel,
"title": log.Title,
"event": log.Event,

8
graphql/resolver/mutations/addPost.go

@ -5,7 +5,7 @@ import (
"time"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/change"
"git.aiterp.net/rpdata/api/model/log"
)
@ -25,8 +25,8 @@ type PostAddArgs struct {
func (r *MutationResolver) AddPost(ctx context.Context, args *PostAddArgs) (*types.PostResolver, error) {
input := args.Input
user := session.FromContext(ctx).User()
if user == nil || !user.Permitted("post.add") {
token := auth.TokenFromContext(ctx)
if !token.Permitted("post.add") {
return nil, ErrUnauthorized
}
@ -45,7 +45,7 @@ func (r *MutationResolver) AddPost(ctx context.Context, args *PostAddArgs) (*typ
return nil, err
}
go change.Submit("Post", "add", user.ID, post.ID, map[string]interface{}{
go change.Submit("Post", "add", token.UserID, post.ID, map[string]interface{}{
"logId": post.LogID,
"time": post.Time,
"kind": post.Kind,

12
graphql/resolver/mutations/addStory.go

@ -5,7 +5,7 @@ import (
"time"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/change"
"git.aiterp.net/rpdata/api/model/story"
)
@ -30,16 +30,16 @@ type StoryAddArgs struct {
func (r *MutationResolver) AddStory(ctx context.Context, args *StoryAddArgs) (*types.StoryResolver, error) {
input := args.Input
user := session.FromContext(ctx).User()
if user == nil || !user.Permitted("member", "story.add") {
token := auth.TokenFromContext(ctx)
if !token.Permitted("member", "story.add") {
return nil, ErrUnauthorized
}
author := user.ID
author := token.UserID
if input.Author != nil {
author = *input.Author
if user.ID != author && !user.Permitted("story.add") {
if token.UserID != author && !token.Permitted("story.add") {
return nil, ErrPermissionDenied
}
}
@ -73,7 +73,7 @@ func (r *MutationResolver) AddStory(ctx context.Context, args *StoryAddArgs) (*t
return nil, err
}
go change.Submit("Story", "add", user.ID, story.ID, map[string]interface{}{
go change.Submit("Story", "add", token.UserID, story.ID, map[string]interface{}{
"name": input.Name,
"category": input.Category,
"author": input.Author,

10
graphql/resolver/mutations/addStoryTag.go

@ -4,7 +4,7 @@ import (
"context"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/change"
"git.aiterp.net/rpdata/api/model/story"
)
@ -25,8 +25,8 @@ func (r *MutationResolver) AddStoryTag(ctx context.Context, args *StoryTagAddArg
input := args.Input
tag := story.Tag{Kind: input.Tag.Kind, Name: input.Tag.Name}
user := session.FromContext(ctx).User()
if user == nil || !user.Permitted("member", "story.edit") {
token := auth.TokenFromContext(ctx)
if !token.Permitted("member", "story.edit") {
return nil, ErrUnauthorized
}
@ -35,7 +35,7 @@ func (r *MutationResolver) AddStoryTag(ctx context.Context, args *StoryTagAddArg
return nil, err
}
if story.Author != user.ID && !user.Permitted("story.edit") {
if story.Author != token.UserID && !token.Permitted("story.edit") {
return nil, ErrPermissionDenied
}
@ -44,7 +44,7 @@ func (r *MutationResolver) AddStoryTag(ctx context.Context, args *StoryTagAddArg
return nil, err
}
go change.Submit("Story", "add.tag", user.ID, story.ID, map[string]interface{}{
go change.Submit("Story", "add.tag", token.UserID, story.ID, map[string]interface{}{
"kind": tag.Kind,
"name": tag.Name,
})

8
graphql/resolver/mutations/editChannel.go

@ -4,7 +4,7 @@ import (
"context"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/change"
"git.aiterp.net/rpdata/api/model/channel"
)
@ -24,8 +24,8 @@ type ChannelEditArgs struct {
func (r *MutationResolver) EditChannel(ctx context.Context, args *ChannelEditArgs) (*types.ChannelResolver, error) {
input := args.Input
user := session.FromContext(ctx).User()
if user == nil || !user.Permitted("channel.edit") {
token := auth.TokenFromContext(ctx)
if !token.Permitted("channel.edit") {
return nil, ErrUnauthorized
}
@ -39,7 +39,7 @@ func (r *MutationResolver) EditChannel(ctx context.Context, args *ChannelEditArg
return nil, err
}
go change.Submit("Channel", "edit", user.ID, channel.Name, map[string]interface{}{
go change.Submit("Channel", "edit", token.UserID, channel.Name, map[string]interface{}{
"logged": input.Logged,
"hub": input.Hub,
"location": input.LocationName,

10
graphql/resolver/mutations/editChapter.go

@ -5,7 +5,7 @@ import (
"time"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/change"
"git.aiterp.net/rpdata/api/model/story"
)
@ -24,8 +24,8 @@ type EditChapterArgs struct {
func (r *MutationResolver) EditChapter(ctx context.Context, args *EditChapterArgs) (*types.ChapterResolver, error) {
input := args.Input
user := session.FromContext(ctx).User()
if user == nil || !user.Permitted("member", "chapter.edit") {
token := auth.TokenFromContext(ctx)
if !token.Permitted("member", "chapter.edit") {
return nil, ErrUnauthorized
}
@ -34,7 +34,7 @@ func (r *MutationResolver) EditChapter(ctx context.Context, args *EditChapterArg
return nil, err
}
if chapter.Author != user.ID && !user.Permitted("chapter.edit") {
if chapter.Author != token.UserID && !token.Permitted("chapter.edit") {
return nil, ErrPermissionDenied
}
@ -53,7 +53,7 @@ func (r *MutationResolver) EditChapter(ctx context.Context, args *EditChapterArg
return nil, err
}
go change.Submit("Chapter", "edit", user.ID, chapter.ID, map[string]interface{}{
go change.Submit("Chapter", "edit", token.UserID, chapter.ID, map[string]interface{}{
"title": input.Title,
"source": input.Source,
"fictionalDate": fictionalDate,

10
graphql/resolver/mutations/editCharacter.go

@ -4,7 +4,7 @@ import (
"context"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/change"
"git.aiterp.net/rpdata/api/model/character"
)
@ -21,8 +21,8 @@ type CharacterEditInput struct {
func (r *MutationResolver) EditCharacter(ctx context.Context, args struct{ Input *CharacterEditInput }) (*types.CharacterResolver, error) {
input := args.Input
user := session.FromContext(ctx).User()
if user == nil || !user.Permitted("member") {
token := auth.TokenFromContext(ctx)
if !token.Permitted("member") {
return nil, ErrUnauthorized
}
@ -30,7 +30,7 @@ func (r *MutationResolver) EditCharacter(ctx context.Context, args struct{ Input
if err != nil {
return nil, err
}
if character.Author != user.ID && !user.Permitted("character.edit") {
if character.Author != token.UserID && !token.Permitted("character.edit") {
return nil, ErrPermissionDenied
}
@ -54,7 +54,7 @@ func (r *MutationResolver) EditCharacter(ctx context.Context, args struct{ Input
return nil, err
}
go change.Submit("Character", "edit", user.ID, character.ID, map[string]interface{}{
go change.Submit("Character", "edit", token.UserID, character.ID, map[string]interface{}{
"name": character.Name,
"shortName": character.ShortName,
"description": character.Description,

8
graphql/resolver/mutations/editFile.go

@ -4,7 +4,7 @@ import (
"context"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/file"
)
@ -21,8 +21,8 @@ type FileEditArgs struct {
func (r *MutationResolver) EditFile(ctx context.Context, args *FileEditArgs) (*types.FileResolver, error) {
input := args.Input
user := session.FromContext(ctx).User()
if user == nil || !user.Permitted("member") {
token := auth.TokenFromContext(ctx)
if !token.Permitted("member") {
return nil, ErrUnauthorized
}
@ -30,7 +30,7 @@ func (r *MutationResolver) EditFile(ctx context.Context, args *FileEditArgs) (*t
if err != nil {
return nil, err
}
if file.Author != user.ID && !user.Permitted("file.edit") {
if file.Author != token.UserID && !token.Permitted("file.edit") {
return nil, ErrUnauthorized
}

8
graphql/resolver/mutations/editLog.go

@ -4,7 +4,7 @@ import (
"context"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/change"
"git.aiterp.net/rpdata/api/model/log"
)
@ -24,8 +24,8 @@ type LogEditArgs struct {
func (r *MutationResolver) EditLog(ctx context.Context, args *LogEditArgs) (*types.LogResolver, error) {
input := args.Input
user := session.FromContext(ctx).User()
if user == nil || !user.Permitted("log.edit") {
token := auth.TokenFromContext(ctx)
if !token.Permitted("log.edit") {
return nil, ErrUnauthorized
}
@ -39,7 +39,7 @@ func (r *MutationResolver) EditLog(ctx context.Context, args *LogEditArgs) (*typ
return nil, err
}
go change.Submit("Log", "edit", user.ID, log.ID, map[string]interface{}{
go change.Submit("Log", "edit", token.UserID, log.ID, map[string]interface{}{
"channel": log.Channel,
"title": input.Title,
"event": input.Event,

8
graphql/resolver/mutations/editPost.go

@ -5,7 +5,7 @@ import (
"time"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/change"
"git.aiterp.net/rpdata/api/model/log"
)
@ -25,8 +25,8 @@ type PostEditArgs struct {
func (r *MutationResolver) EditPost(ctx context.Context, args *PostEditArgs) (*types.PostResolver, error) {
input := args.Input
user := session.FromContext(ctx).User()
if user == nil || !user.Permitted("post.edit") {
token := auth.TokenFromContext(ctx)
if !token.Permitted("post.edit") {
return nil, ErrUnauthorized
}
@ -50,7 +50,7 @@ func (r *MutationResolver) EditPost(ctx context.Context, args *PostEditArgs) (*t
return nil, err
}
go change.Submit("Post", "edit", user.ID, post.ID, map[string]interface{}{
go change.Submit("Post", "edit", token.UserID, post.ID, map[string]interface{}{
"time": postTime,
"kind": input.Kind,
"nick": input.Nick,

10
graphql/resolver/mutations/editStory.go

@ -5,7 +5,7 @@ import (
"time"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/change"
"git.aiterp.net/rpdata/api/model/story"
)
@ -27,8 +27,8 @@ type StoryEditArgs struct {
func (r *MutationResolver) EditStory(ctx context.Context, args *StoryEditArgs) (*types.StoryResolver, error) {
input := args.Input
user := session.FromContext(ctx).User()
if user == nil || !user.Permitted("member", "story.edit") {
token := auth.TokenFromContext(ctx)
if !token.Permitted("member", "story.edit") {
return nil, ErrUnauthorized
}
@ -37,7 +37,7 @@ func (r *MutationResolver) EditStory(ctx context.Context, args *StoryEditArgs) (
return nil, err
}
if story.Author != user.ID && !user.Permitted("story.edit") {
if story.Author != token.UserID && !token.Permitted("story.edit") {
return nil, ErrPermissionDenied
}
@ -56,7 +56,7 @@ func (r *MutationResolver) EditStory(ctx context.Context, args *StoryEditArgs) (
return nil, err
}
go change.Submit("Story", "edit", user.ID, story.ID, map[string]interface{}{
go change.Submit("Story", "edit", token.UserID, story.ID, map[string]interface{}{
"name": input.Name,
"category": input.Category,
"author": input.Author,

8
graphql/resolver/mutations/movePost.go

@ -4,7 +4,7 @@ import (
"context"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/change"
"git.aiterp.net/rpdata/api/model/log"
)
@ -21,8 +21,8 @@ type PostMoveArgs struct {
func (r *MutationResolver) MovePost(ctx context.Context, args *PostMoveArgs) (*types.PostResolver, error) {
input := args.Input
user := session.FromContext(ctx).User()
if user == nil || !user.Permitted("post.move") {
token := auth.TokenFromContext(ctx)
if !token.Permitted("post.move") {
return nil, ErrUnauthorized
}
@ -36,7 +36,7 @@ func (r *MutationResolver) MovePost(ctx context.Context, args *PostMoveArgs) (*t
return nil, err
}
go change.Submit("Post", "move", user.ID, post.ID, map[string]interface{}{
go change.Submit("Post", "move", token.UserID, post.ID, map[string]interface{}{
"logId": post.LogID,
"targetIndex": input.ToPosition,
})

8
graphql/resolver/mutations/removeChannel.go

@ -4,7 +4,7 @@ import (
"context"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/change"
"git.aiterp.net/rpdata/api/model/channel"
)
@ -16,8 +16,8 @@ type RemoveChannelArgs struct {
// RemoveChannel resolves the editChannel mutation
func (r *MutationResolver) RemoveChannel(ctx context.Context, args RemoveChannelArgs) (*types.ChannelResolver, error) {
user := session.FromContext(ctx).User()
if user == nil || !user.Permitted("channel.remove") {
token := auth.TokenFromContext(ctx)
if !token.Permitted("channel.remove") {
return nil, ErrUnauthorized
}
@ -31,7 +31,7 @@ func (r *MutationResolver) RemoveChannel(ctx context.Context, args RemoveChannel
return nil, err
}
go change.Submit("Channel", "remove", user.ID, channel.Name, nil)
go change.Submit("Channel", "remove", token.UserID, channel.Name, nil)
return &types.ChannelResolver{C: channel}, nil
}

8
graphql/resolver/mutations/removeChapter.go

@ -4,7 +4,7 @@ import (
"context"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/change"
"git.aiterp.net/rpdata/api/model/story"
)
@ -16,8 +16,8 @@ type RemoveChapterArgs struct {
// RemoveChapter resolves the removeChapter mutation
func (r *MutationResolver) RemoveChapter(ctx context.Context, args *RemoveChapterArgs) (*types.ChapterResolver, error) {
user := session.FromContext(ctx).User()
if user == nil || !user.Permitted("member", "chapter.edit") {
token := auth.TokenFromContext(ctx)
if !token.Permitted("member", "chapter.edit") {
return nil, ErrUnauthorized
}
@ -31,7 +31,7 @@ func (r *MutationResolver) RemoveChapter(ctx context.Context, args *RemoveChapte
return nil, err
}
go change.Submit("Chapter", "remove", user.ID, chapter.ID, nil)
go change.Submit("Chapter", "remove", token.UserID, chapter.ID, nil)
return &types.ChapterResolver{C: chapter}, nil
}

10
graphql/resolver/mutations/removeCharacter.go

@ -4,7 +4,7 @@ import (
"context"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/change"
"git.aiterp.net/rpdata/api/model/character"
)
@ -16,8 +16,8 @@ type RemoveCharacterArgs struct {
// RemoveCharacter resolves the removeCharacter mutation
func (r *MutationResolver) RemoveCharacter(ctx context.Context, args RemoveCharacterArgs) (*types.CharacterResolver, error) {
user := session.FromContext(ctx).User()
if user == nil || !user.Permitted("member") {
token := auth.TokenFromContext(ctx)
if !token.Permitted("member") {
return nil, ErrUnauthorized
}
@ -25,7 +25,7 @@ func (r *MutationResolver) RemoveCharacter(ctx context.Context, args RemoveChara
if err != nil {
return nil, err
}
if character.Author != user.ID && !user.Permitted("character.remove") {
if character.Author != token.UserID && !token.Permitted("character.remove") {
return nil, ErrPermissionDenied
}
@ -34,7 +34,7 @@ func (r *MutationResolver) RemoveCharacter(ctx context.Context, args RemoveChara
return nil, err
}
go change.Submit("Character", "remove", user.ID, character.ID, map[string]interface{}{
go change.Submit("Character", "remove", token.UserID, character.ID, map[string]interface{}{
"name": character.Name,
"author": character.Author,
"nicks": character.Nicks,

10
graphql/resolver/mutations/removeCharacterNick.go

@ -4,7 +4,7 @@ import (
"context"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/change"
"git.aiterp.net/rpdata/api/model/character"
"git.aiterp.net/rpdata/api/model/log"
@ -20,8 +20,8 @@ type RemoveCharacterNick struct {
func (r *MutationResolver) RemoveCharacterNick(ctx context.Context, args struct{ Input *RemoveCharacterNick }) (*types.CharacterResolver, error) {
input := args.Input
user := session.FromContext(ctx).User()
if user == nil || !user.Permitted("member") {
token := auth.TokenFromContext(ctx)
if !token.Permitted("member") {
return nil, ErrUnauthorized
}
@ -29,7 +29,7 @@ func (r *MutationResolver) RemoveCharacterNick(ctx context.Context, args struct{
if err != nil {
return nil, err
}
if character.Author != user.ID && !user.Permitted("character.edit") {
if character.Author != token.UserID && !token.Permitted("character.edit") {
return nil, ErrPermissionDenied
}
@ -38,7 +38,7 @@ func (r *MutationResolver) RemoveCharacterNick(ctx context.Context, args struct{
return nil, err
}
go change.Submit("Character", "remove.nick", user.ID, character.ID, map[string]interface{}{
go change.Submit("Character", "remove.nick", token.UserID, character.ID, map[string]interface{}{
"nick": input.Nick,
})

8
graphql/resolver/mutations/removeFile.go

@ -4,7 +4,7 @@ import (
"context"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/file"
)
@ -15,8 +15,8 @@ type RemoveFileArgs struct {
// RemoveFile resolves the removeFIle mutation
func (r *MutationResolver) RemoveFile(ctx context.Context, args *RemoveFileArgs) (*types.FileResolver, error) {
user := session.FromContext(ctx).User()
if user == nil || !user.Permitted("member") {
token := auth.TokenFromContext(ctx)
if !token.Permitted("member") {
return nil, ErrUnauthorized
}
@ -24,7 +24,7 @@ func (r *MutationResolver) RemoveFile(ctx context.Context, args *RemoveFileArgs)
if err != nil {
return nil, err
}
if file.Author != user.ID && !user.Permitted("file.remove") {
if file.Author != token.UserID && !token.Permitted("file.remove") {
return nil, ErrUnauthorized
}

8
graphql/resolver/mutations/removeLog.go

@ -4,7 +4,7 @@ import (
"context"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/change"
"git.aiterp.net/rpdata/api/model/log"
)
@ -16,8 +16,8 @@ type RemoveLogArgs struct {
// RemoveLog resolves the removeLog mutation
func (r *MutationResolver) RemoveLog(ctx context.Context, args *RemoveLogArgs) (*types.LogResolver, error) {
user := session.FromContext(ctx).User()
if user == nil || !user.Permitted("log.remove") {
token := auth.TokenFromContext(ctx)
if !token.Permitted("log.remove") {
return nil, ErrUnauthorized
}
@ -31,7 +31,7 @@ func (r *MutationResolver) RemoveLog(ctx context.Context, args *RemoveLogArgs) (
return nil, err
}
go change.Submit("Log", "remove", user.ID, log.ID, nil)
go change.Submit("Log", "remove", token.UserID, log.ID, nil)
return &types.LogResolver{L: log}, nil
}

8
graphql/resolver/mutations/removePost.go

@ -4,7 +4,7 @@ import (
"context"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/change"
"git.aiterp.net/rpdata/api/model/log"
)
@ -16,8 +16,8 @@ type PostRemoveArgs struct {
// RemovePost resolves the removePost mutation
func (r *MutationResolver) RemovePost(ctx context.Context, args PostRemoveArgs) (*types.PostResolver, error) {
user := session.FromContext(ctx).User()
if user == nil || !user.Permitted("post.remove") {
token := auth.TokenFromContext(ctx)
if !token.Permitted("post.remove") {
return nil, ErrUnauthorized
}
@ -26,7 +26,7 @@ func (r *MutationResolver) RemovePost(ctx context.Context, args PostRemoveArgs)
return nil, err
}
go change.Submit("Post", "remove", user.ID, post.ID, map[string]interface{}{
go change.Submit("Post", "remove", token.UserID, post.ID, map[string]interface{}{
"logId": post.LogID,
})

10
graphql/resolver/mutations/removeStory.go

@ -4,7 +4,7 @@ import (
"context"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/change"
"git.aiterp.net/rpdata/api/model/story"
)
@ -16,8 +16,8 @@ type StoryRemoveArgs struct {
// RemoveStory resolves the removeStory mutation
func (r *MutationResolver) RemoveStory(ctx context.Context, args *StoryRemoveArgs) (*types.StoryResolver, error) {
user := session.FromContext(ctx).User()
if user == nil || !user.Permitted("member", "story.edit") {
token := auth.TokenFromContext(ctx)
if !token.Permitted("member", "story.edit") {
return nil, ErrUnauthorized
}
@ -26,7 +26,7 @@ func (r *MutationResolver) RemoveStory(ctx context.Context, args *StoryRemoveArg
return nil, err
}
if story.Author != user.ID && !user.Permitted("story.remove") {
if story.Author != token.UserID && !token.Permitted("story.remove") {
return nil, ErrPermissionDenied
}
@ -35,7 +35,7 @@ func (r *MutationResolver) RemoveStory(ctx context.Context, args *StoryRemoveArg
return nil, err
}
go change.Submit("Story", "remove", user.ID, story.ID, nil)
go change.Submit("Story", "remove", token.UserID, story.ID, nil)
return &types.StoryResolver{S: story}, nil
}

10
graphql/resolver/mutations/removeStoryTag.go

@ -4,7 +4,7 @@ import (
"context"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/change"
"git.aiterp.net/rpdata/api/model/story"
)
@ -25,8 +25,8 @@ func (r *MutationResolver) RemoveStoryTag(ctx context.Context, args *StoryTagRem
input := args.Input
tag := story.Tag{Kind: input.Tag.Kind, Name: input.Tag.Name}
user := session.FromContext(ctx).User()
if user == nil || !user.Permitted("member", "story.edit") {
token := auth.TokenFromContext(ctx)
if !token.Permitted("member", "story.edit") {
return nil, ErrUnauthorized
}
@ -35,7 +35,7 @@ func (r *MutationResolver) RemoveStoryTag(ctx context.Context, args *StoryTagRem
return nil, err
}
if story.Author != user.ID && !user.Permitted("story.edit") {
if story.Author != token.UserID && !token.Permitted("story.edit") {
return nil, ErrPermissionDenied
}
@ -44,7 +44,7 @@ func (r *MutationResolver) RemoveStoryTag(ctx context.Context, args *StoryTagRem
return nil, err
}
go change.Submit("Story", "remove.tag", user.ID, story.ID, map[string]interface{}{
go change.Submit("Story", "remove.tag", token.UserID, story.ID, map[string]interface{}{
"kind": tag.Kind,
"name": tag.Name,
})

8
graphql/resolver/queries/files.go

@ -4,7 +4,7 @@ import (
"context"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/file"
)
@ -19,11 +19,11 @@ type FilesArgs struct {
// Files resolves the file query
func (r *QueryResolver) Files(ctx context.Context, args *FilesArgs) ([]*types.FileResolver, error) {
filter := args.Filter
user := session.FromContext(ctx).User()
token := auth.TokenFromContext(ctx)
author := ""
if user != nil {
author = user.ID
if token != nil {
author = token.UserID
}
mimeTypes := []string(nil)

11
graphql/resolver/queries/stories.go

@ -5,7 +5,7 @@ import (
"time"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/model/story"
)
@ -30,6 +30,8 @@ type StoriesArg struct {
func (r *QueryResolver) Stories(ctx context.Context, args *StoriesArg) ([]*types.StoryResolver, error) {
var err error
token := auth.TokenFromContext(ctx)
filter := args.Filter
author := ""
@ -79,16 +81,15 @@ func (r *QueryResolver) Stories(ctx context.Context, args *StoriesArg) ([]*types
unlisted = filter.Unlisted != nil && *filter.Unlisted == true
if unlisted {
user := session.FromContext(ctx).User()
if user == nil {
if token == nil {
return nil, ErrUnauthorized
}
if author != "" && author != user.ID && !user.Permitted("story.unlisted") {
if author != "" && author != token.UserID && !token.Permitted("story.unlisted") {
return nil, ErrPermissionDenied
}
author = user.ID
author = token.UserID
}
open = filter.Open

16
graphql/resolver/types/session.go

@ -1,16 +0,0 @@
package types
import "git.aiterp.net/rpdata/api/internal/session"
// SessionResolver resolves Session
type SessionResolver struct{ S *session.Session }
// User resolves Session.user
func (r *SessionResolver) User() *UserResolver {
user := r.S.User()
if user == nil {
return nil
}
return &UserResolver{U: user}
}

16
graphql/resolver/types/user.go

@ -1,16 +0,0 @@
package types
import "git.aiterp.net/rpdata/api/internal/session"
// UserResolver resulves the user type
type UserResolver struct{ U *session.User }
// ID resolves User.id
func (r *UserResolver) ID() string {
return r.U.ID
}
// Permissions resolves User.permissions
func (r *UserResolver) Permissions() []string {
return r.U.Permissions
}

109
internal/auth/key.go

@ -0,0 +1,109 @@
package auth
import (
"crypto/rand"
"encoding/binary"
"errors"
"strconv"
"git.aiterp.net/rpdata/api/internal/store"
"github.com/globalsign/mgo"
)
var keyCollection *mgo.Collection
// A Key contains a JWT secret and the limitations of it. There are two types of
// keys, single-user keys and wildcard keys. The former is used to authenticate
// a single user (e.g. the logbot) through an API while the latter is only for
// services that can be trusted to perform its own authentication (a frontend).
type Key struct {
ID string `bson:"_id"`
Name string `bson:"name"`
User string `bson:"user"`
Secret string `bson:"secret"`
}
// ValidForUser returns true if the key's user is the same as
// the user, or it's a wildcard key.
func (key *Key) ValidForUser(user string) bool {
return key.User == user || key.User == "*"
}
// FindKey finds a key by kid (key ID)
func FindKey(kid string) (Key, error) {
key := Key{}
err := keyCollection.FindId(kid).One(&key)
return key, err
}
// NewKey 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 NewKey(name, user string) (*Key, error) {
if user == "*" {
return nil, errors.New("auth: wildcard keys not allowed")
}
secret, err := makeKeySecret()
if err != nil {
return nil, err
}
key := &Key{
ID: makeKeyID(),
Name: name,
User: user,
Secret: secret,
}
if err := keyCollection.Insert(key); err != nil {
return nil, err
}
return key, nil
}
func init() {
store.HandleInit(func(db *mgo.Database) {
keyCollection = db.C("auth.keys")
keyCollection.EnsureIndexKey("user")
})
}
// makeKeyID makes a random story ID that's 16 characters long
func makeKeyID() string {
result := "K"
offset := 0
data := make([]byte, 32)
rand.Read(data)
for len(result) < 16 {
result += strconv.FormatUint(binary.LittleEndian.Uint64(data[offset:]), 36)
offset += 8
if offset >= 32 {
rand.Read(data)
offset = 0
}
}
return result[:16]
}
func makeKeySecret() (string, error) {
data := make([]byte, 64)
alphabet := "0123456789abcdefghjiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSUTVWXYZ-_"
_, err := rand.Read(data)
if err != nil {
return "", err
}
for i := range data {
data[i] = alphabet[data[i]%64]
}
return string(data), nil
}

198
internal/auth/token.go

@ -0,0 +1,198 @@
package auth
import (
"context"
"errors"
"fmt"
"net/http"
"strings"
"time"
jwt "github.com/dgrijalva/jwt-go"
)
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")
// ErrWrongPermissions is returned by CheckToken if the key cannot claim one or more of its permissions
var ErrWrongPermissions = 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")
// 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)
if !ok {
return nil
}
return token
}
// RequestWithToken either returns the request, or the request with a new context that
// has the token.
func RequestWithToken(r *http.Request) *http.Request {
header := r.Header.Get("Authorization")
if header == "" {
return r
}
if !strings.HasPrefix(header, "Bearer ") {
return r
}
token, err := CheckToken(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 CheckToken(tokenString string) (token Token, err error) {
var key 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 = FindKey(kid)
if err != nil || key.ID == "" {
return nil, ErrKeyNotFound
}
return []byte(key.Secret), nil
})
if err != nil {
return Token{}, err
}
userid, permissions, err := parseClaims(jwtToken.Claims)
if err != nil {
return Token{}, err
}
if !key.ValidForUser(userid) {
return Token{}, ErrWrongUser
}
user, err := FindUser(userid)
if err != nil {
return Token{}, ErrDeletedUser
}
for _, permission := range permissions {
found := false
for _, userPermission := range user.Permissions {
if permission == userPermission {
found = true
break
}
}
if !found {
return Token{}, ErrWrongPermissions
}
}
return Token{UserID: token.UserID, Permissions: permissions}, nil
}
func 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
}

4
internal/session/user.go → internal/auth/user.go

@ -1,4 +1,4 @@
package session
package auth
import (
"git.aiterp.net/rpdata/api/internal/store"
@ -16,7 +16,7 @@ type User struct {
// Permitted returns true if either of the permissions can be found
//
// `user.ID == page.Author || user.Permitted("story.edit")`
// `token.UserID == page.Author || token.Permitted("story.edit")`
func (user *User) Permitted(permissions ...string) bool {
for i := range permissions {
for j := range user.Permissions {

20
internal/session/context.go

@ -1,20 +0,0 @@
package session
import "context"
type contextKeyType struct{ name string }
func (ck *contextKeyType) String() string {
return ck.name
}
var contextKey = &contextKeyType{name: "session context key"}
// FromContext gets a session fron the context.
func FromContext(ctx context.Context) *Session {
return ctx.Value(contextKey).(*Session)
}
func contextWithSession(parent context.Context, session *Session) context.Context {
return context.WithValue(parent, contextKey, session)
}

40
internal/session/defaults.go

@ -1,40 +0,0 @@
package session
// DefaultPermissions gets the default permissions
func DefaultPermissions() []string {
return []string{
"member",
"log.edit",
"log.reorder",
"post.edit",
"post.move",
"file.upload",
}
}
// AllPermissions gets all permissions and their purpose
func 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",
"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.edit": "Can edit non-owned stories",
"story.remove": "Can remove non-owned stories",
"story.unlisted": "Can view unlisted stories by other users",
"file.upload": "Can upload files",
"file.edit": "Can edit non-owned files",
"file.remove": "Can remove non-owned files",
}
}

182
internal/session/session.go

@ -1,182 +0,0 @@
package session
import (
"crypto/rand"
"encoding/hex"
"log"
"net/http"
"strings"
"sync"
"time"
"git.aiterp.net/aiterp/wikiauth"
"git.aiterp.net/rpdata/api/internal/config"
"git.aiterp.net/rpdata/api/internal/store"
"github.com/globalsign/mgo"
"github.com/globalsign/mgo/bson"
)
var sessionCollection *mgo.Collection
// A Session represents a login session.
type Session struct {
mutex sync.Mutex
ID string `bson:"_id"`
Time time.Time `bson:"time"`
UserID string `bson:"userId"`
user *User
w http.ResponseWriter
}
// Load loads a session from a cookie, returning either `r` or a request
// with the session context.
func Load(w http.ResponseWriter, r *http.Request) *http.Request {
cookie, err := r.Cookie("aiterp_session")
if err != nil {
return r.WithContext(contextWithSession(r.Context(), &Session{w: w}))
}
id := cookie.Value
session := Session{}
err = sessionCollection.FindId(id).One(&session)
if err != nil || time.Since(session.Time) > time.Hour*168 {
return r.WithContext(contextWithSession(r.Context(), &Session{w: w}))
}
if session.ID != "" && time.Since(session.Time) > time.Second*30 {
session.Time = time.Now()
go sessionCollection.UpdateId(id, bson.M{"$set": bson.M{"time": session.Time}})
}
cookie.Expires = time.Now().Add(time.Hour * 168)
http.SetCookie(w, cookie)
session.w = w
return r.WithContext(contextWithSession(r.Context(), &session))
}
// Login logs a user in.
func (session *Session) Login(username, password string) error {
auth := wikiauth.New(config.Global().Wiki.URL)
err := auth.Login(username, password)
if err != nil {
return err
}
// Allow bot passwords
username = strings.SplitN(username, "@", 2)[0]
data := make([]byte, 32)
_, err = rand.Read(data)
if err != nil {
return err
}
session.ID = hex.EncodeToString(data)
session.UserID = username
session.Time = time.Now()
err = sessionCollection.Insert(&session)
if err != nil {
return err
}
http.SetCookie(session.w, &http.Cookie{
Name: "aiterp_session",
Value: session.ID,
Expires: time.Now().Add(time.Hour * 2160), // 90 days
HttpOnly: true,
})
user, err := FindUser(session.UserID)
if err == mgo.ErrNotFound {
user = User{ID: username, Nick: "", Permissions: DefaultPermissions()}
err := userCollection.Insert(user)
if err != nil {
return err
}
} else if err != nil {
return err
}
return nil
}
// Logout logs out the session
func (session *Session) Logout() {
http.SetCookie(session.w, &http.Cookie{
Name: "aiterp_session",
Value: "",
Expires: time.Unix(0, 0),
HttpOnly: true,
})
session.mutex.Lock()
session.user = nil
session.UserID = ""
session.ID = ""
session.mutex.Unlock()
sessionCollection.RemoveId(session.ID)
}
// User gets the user information for the session.
func (session *Session) User() *User {
session.mutex.Lock()
defer session.mutex.Unlock()
if session.user != nil {
return session.user
}
if session.UserID == "" {
return nil
}
user, err := FindUser(session.UserID)
if err != nil {
return nil
}
return &user
}
// NameOrPermitted is a shorthand for checking the username OR permissions, e.g. to check
// if a logged in user can edit a certain post.
func (session *Session) NameOrPermitted(userid string, permissions ...string) bool {
if session.UserID == userid {
return true
}
user := session.User()
if user == nil {
return false
}
return user.Permitted()
}
func init() {
store.HandleInit(func(db *mgo.Database) {
sessionCollection = db.C("core.sessions")
sessionCollection.EnsureIndexKey("nick")
sessionCollection.EnsureIndexKey("userId")
err := sessionCollection.EnsureIndex(mgo.Index{
Name: "time",
Key: []string{"time"},
ExpireAfter: time.Hour * 168,
})
if err != nil {
log.Fatalln(err)
}
})
}
Loading…
Cancel
Save