diff --git a/cmd/rpdata-graphiql/main.go b/cmd/rpdata-graphiql/main.go index 932a22b..02a7193 100644 --- a/cmd/rpdata-graphiql/main.go +++ b/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 diff --git a/graphql/resolver/mutations/addChannel.go b/graphql/resolver/mutations/addChannel.go index a4b6876..783e563 100644 --- a/graphql/resolver/mutations/addChannel.go +++ b/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, diff --git a/graphql/resolver/mutations/addChapter.go b/graphql/resolver/mutations/addChapter.go index 1ab302b..0f772a3 100644 --- a/graphql/resolver/mutations/addChapter.go +++ b/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, diff --git a/graphql/resolver/mutations/addCharacter.go b/graphql/resolver/mutations/addCharacter.go index 8fc60e4..0dff65a 100644 --- a/graphql/resolver/mutations/addCharacter.go +++ b/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, diff --git a/graphql/resolver/mutations/addCharacterNick.go b/graphql/resolver/mutations/addCharacterNick.go index b07e0ea..da0789f 100644 --- a/graphql/resolver/mutations/addCharacterNick.go +++ b/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, }) diff --git a/graphql/resolver/mutations/addLog.go b/graphql/resolver/mutations/addLog.go index 5fa7852..0807840 100644 --- a/graphql/resolver/mutations/addLog.go +++ b/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, diff --git a/graphql/resolver/mutations/addPost.go b/graphql/resolver/mutations/addPost.go index 7989191..b6e2d12 100644 --- a/graphql/resolver/mutations/addPost.go +++ b/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, diff --git a/graphql/resolver/mutations/addStory.go b/graphql/resolver/mutations/addStory.go index 1cc6d50..879dfcf 100644 --- a/graphql/resolver/mutations/addStory.go +++ b/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, diff --git a/graphql/resolver/mutations/addStoryTag.go b/graphql/resolver/mutations/addStoryTag.go index b49d68d..e15c915 100644 --- a/graphql/resolver/mutations/addStoryTag.go +++ b/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, }) diff --git a/graphql/resolver/mutations/editChannel.go b/graphql/resolver/mutations/editChannel.go index 6b4db7a..5f16ce8 100644 --- a/graphql/resolver/mutations/editChannel.go +++ b/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, diff --git a/graphql/resolver/mutations/editChapter.go b/graphql/resolver/mutations/editChapter.go index 38dcd29..24e14f2 100644 --- a/graphql/resolver/mutations/editChapter.go +++ b/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, diff --git a/graphql/resolver/mutations/editCharacter.go b/graphql/resolver/mutations/editCharacter.go index 0fb79b6..b2cee3a 100644 --- a/graphql/resolver/mutations/editCharacter.go +++ b/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, diff --git a/graphql/resolver/mutations/editFile.go b/graphql/resolver/mutations/editFile.go index 454cba5..f916098 100644 --- a/graphql/resolver/mutations/editFile.go +++ b/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 } diff --git a/graphql/resolver/mutations/editLog.go b/graphql/resolver/mutations/editLog.go index 4d54e07..6b6d9a3 100644 --- a/graphql/resolver/mutations/editLog.go +++ b/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, diff --git a/graphql/resolver/mutations/editPost.go b/graphql/resolver/mutations/editPost.go index 002e9b7..5298428 100644 --- a/graphql/resolver/mutations/editPost.go +++ b/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, diff --git a/graphql/resolver/mutations/editStory.go b/graphql/resolver/mutations/editStory.go index 55b07c0..72093e9 100644 --- a/graphql/resolver/mutations/editStory.go +++ b/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, diff --git a/graphql/resolver/mutations/movePost.go b/graphql/resolver/mutations/movePost.go index 7e48d6c..e6c8708 100644 --- a/graphql/resolver/mutations/movePost.go +++ b/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, }) diff --git a/graphql/resolver/mutations/removeChannel.go b/graphql/resolver/mutations/removeChannel.go index 8b47b86..ba64f62 100644 --- a/graphql/resolver/mutations/removeChannel.go +++ b/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 } diff --git a/graphql/resolver/mutations/removeChapter.go b/graphql/resolver/mutations/removeChapter.go index 2350e20..8a67efd 100644 --- a/graphql/resolver/mutations/removeChapter.go +++ b/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 } diff --git a/graphql/resolver/mutations/removeCharacter.go b/graphql/resolver/mutations/removeCharacter.go index 7fefcbb..3dcffb8 100644 --- a/graphql/resolver/mutations/removeCharacter.go +++ b/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, diff --git a/graphql/resolver/mutations/removeCharacterNick.go b/graphql/resolver/mutations/removeCharacterNick.go index 4d9cd42..2f3e95b 100644 --- a/graphql/resolver/mutations/removeCharacterNick.go +++ b/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, }) diff --git a/graphql/resolver/mutations/removeFile.go b/graphql/resolver/mutations/removeFile.go index 61be7db..270f0cb 100644 --- a/graphql/resolver/mutations/removeFile.go +++ b/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 } diff --git a/graphql/resolver/mutations/removeLog.go b/graphql/resolver/mutations/removeLog.go index af822bb..60ba5b4 100644 --- a/graphql/resolver/mutations/removeLog.go +++ b/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 } diff --git a/graphql/resolver/mutations/removePost.go b/graphql/resolver/mutations/removePost.go index f656dbd..9a058a2 100644 --- a/graphql/resolver/mutations/removePost.go +++ b/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, }) diff --git a/graphql/resolver/mutations/removeStory.go b/graphql/resolver/mutations/removeStory.go index d077ff8..497649b 100644 --- a/graphql/resolver/mutations/removeStory.go +++ b/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 } diff --git a/graphql/resolver/mutations/removeStoryTag.go b/graphql/resolver/mutations/removeStoryTag.go index 05fbb1b..10ffb01 100644 --- a/graphql/resolver/mutations/removeStoryTag.go +++ b/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, }) diff --git a/graphql/resolver/queries/files.go b/graphql/resolver/queries/files.go index 283c4e2..cf604dd 100644 --- a/graphql/resolver/queries/files.go +++ b/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) diff --git a/graphql/resolver/queries/stories.go b/graphql/resolver/queries/stories.go index 05a5e80..86fa9f1 100644 --- a/graphql/resolver/queries/stories.go +++ b/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 diff --git a/graphql/resolver/types/session.go b/graphql/resolver/types/session.go deleted file mode 100644 index 8e95255..0000000 --- a/graphql/resolver/types/session.go +++ /dev/null @@ -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} -} diff --git a/graphql/resolver/types/user.go b/graphql/resolver/types/user.go deleted file mode 100644 index 290cbfe..0000000 --- a/graphql/resolver/types/user.go +++ /dev/null @@ -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 -} diff --git a/internal/auth/key.go b/internal/auth/key.go new file mode 100644 index 0000000..592cfba --- /dev/null +++ b/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 +} diff --git a/internal/auth/token.go b/internal/auth/token.go new file mode 100644 index 0000000..5885b51 --- /dev/null +++ b/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 +} diff --git a/internal/session/user.go b/internal/auth/user.go similarity index 92% rename from internal/session/user.go rename to internal/auth/user.go index dc6eb5c..d9db5fb 100644 --- a/internal/session/user.go +++ b/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 { diff --git a/internal/session/context.go b/internal/session/context.go deleted file mode 100644 index 03bb2b9..0000000 --- a/internal/session/context.go +++ /dev/null @@ -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) -} diff --git a/internal/session/defaults.go b/internal/session/defaults.go deleted file mode 100644 index 35ba45d..0000000 --- a/internal/session/defaults.go +++ /dev/null @@ -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", - } -} diff --git a/internal/session/session.go b/internal/session/session.go deleted file mode 100644 index 97138c6..0000000 --- a/internal/session/session.go +++ /dev/null @@ -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) - } - }) -}