Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
Marco Realacci 2022-11-19 14:59:09 +01:00
commit 519ae22197
12 changed files with 231 additions and 13 deletions

View file

@ -28,7 +28,6 @@ func (rt *_router) wrap(fn httpRouterHandler) func(http.ResponseWriter, *http.Re
if err != nil { if err != nil {
rt.baseLogger.WithError(err).Info("User not authorized") rt.baseLogger.WithError(err).Info("User not authorized")
w.WriteHeader(http.StatusUnauthorized)
return return
} }

View file

@ -8,6 +8,11 @@ import (
func (rt *_router) Handler() http.Handler { func (rt *_router) Handler() http.Handler {
// Register routes // Register routes
rt.router.POST("/session", rt.wrap(rt.PostSession)) rt.router.POST("/session", rt.wrap(rt.PostSession))
rt.router.PUT("/users/:user_id/username", rt.wrap(rt.UpdateUsername))
rt.router.GET("/users/:user_id/followers", rt.wrap(rt.GetFollowers))
rt.router.GET("/", rt.getHelloWorld) rt.router.GET("/", rt.getHelloWorld)
rt.router.GET("/context", rt.wrap(rt.getContextReply)) rt.router.GET("/context", rt.wrap(rt.getContextReply))

View file

@ -4,6 +4,7 @@ import (
"errors" "errors"
"strings" "strings"
"github.com/notherealmarco/WASAPhoto/service/api/reqcontext"
"github.com/notherealmarco/WASAPhoto/service/database" "github.com/notherealmarco/WASAPhoto/service/database"
) )
@ -32,19 +33,40 @@ func (b *BearerAuth) GetToken() string {
return b.token return b.token
} }
func (b *BearerAuth) Authorized(db database.AppDatabase) (bool, error) { func (b *BearerAuth) Authorized(db database.AppDatabase) (reqcontext.AuthStatus, error) {
// this is the way we manage authorization, the bearer token is the user id // this is the way we manage authorization, the bearer token is the user id
state, err := db.UserExists(b.token) state, err := db.UserExists(b.token)
if err != nil { if err != nil {
return false, err return reqcontext.UNAUTHORIZED, err
} }
return state, nil
if state {
return reqcontext.AUTHORIZED, nil
}
return reqcontext.UNAUTHORIZED, nil
}
func (b *BearerAuth) UserAuthorized(db database.AppDatabase, uid string) (reqcontext.AuthStatus, error) {
// If uid is not a valid user, return USER_NOT_FOUND
user_exists, err := db.UserExists(uid)
if err != nil {
return reqcontext.UNAUTHORIZED, err
}
if !user_exists {
return reqcontext.USER_NOT_FOUND, nil
} }
func (b *BearerAuth) UserAuthorized(db database.AppDatabase, uid string) (bool, error) {
if b.token == uid { if b.token == uid {
return b.Authorized(db) auth, err := b.Authorized(db)
if err != nil {
return -1, err
} }
return false, nil
return auth, nil
}
return reqcontext.FORBIDDEN, nil
} }

View file

@ -2,8 +2,10 @@ package authorization
import ( import (
"errors" "errors"
"net/http"
"github.com/notherealmarco/WASAPhoto/service/api/reqcontext" "github.com/notherealmarco/WASAPhoto/service/api/reqcontext"
"github.com/notherealmarco/WASAPhoto/service/database"
) )
func BuildAuth(header string) (reqcontext.Authorization, error) { func BuildAuth(header string) (reqcontext.Authorization, error) {
@ -16,3 +18,26 @@ func BuildAuth(header string) (reqcontext.Authorization, error) {
} }
return auth, nil return auth, nil
} }
func SendAuthorizationError(f func(db database.AppDatabase, uid string) (reqcontext.AuthStatus, error), uid string, db database.AppDatabase, w http.ResponseWriter) bool {
auth, err := f(db, uid)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
// todo: log error and write it to the response
return false
}
if auth == reqcontext.UNAUTHORIZED {
w.WriteHeader(http.StatusUnauthorized)
return false
}
if auth == reqcontext.FORBIDDEN {
w.WriteHeader(http.StatusForbidden)
return false
}
// requested user is not found -> 404 as the resource is not found
if auth == reqcontext.USER_NOT_FOUND {
w.WriteHeader(http.StatusNotFound)
return false
}
return true
}

49
service/api/followers.go Normal file
View file

@ -0,0 +1,49 @@
package api
import (
"encoding/json"
"net/http"
"github.com/julienschmidt/httprouter"
"github.com/notherealmarco/WASAPhoto/service/api/helpers"
"github.com/notherealmarco/WASAPhoto/service/api/reqcontext"
)
func (rt *_router) GetFollowers(w http.ResponseWriter, r *http.Request, ps httprouter.Params, ctx reqcontext.RequestContext) {
uid := ps.ByName("user_id")
if !helpers.VerifyUserOrNotFound(rt.db, uid, w) {
return
}
followers, err := rt.db.GetUserFollowers(uid)
if err != nil {
w.WriteHeader(http.StatusInternalServerError) // todo: is not ok, maybe let's use a helper
return
}
w.Header().Set("content-type", "application/json")
err = json.NewEncoder(w).Encode(&followers)
if err != nil {
w.WriteHeader(http.StatusInternalServerError) // todo: is not ok
return
}
}
func (rt *_router) PutFollow(w http.ResponseWriter, r *http.Request, ps httprouter.Params, ctx reqcontext.RequestContext) {
uid := ps.ByName("user_id")
followed := ps.ByName("follower_uid")
err := rt.db.FollowUser(uid, followed)
if err != nil {
w.WriteHeader(http.StatusInternalServerError) // todo: is not ok, maybe let's use a helper
return
}
w.WriteHeader(http.StatusNoContent) // todo: change to 204 also in API spec
}

View file

@ -0,0 +1,42 @@
package helpers
import (
"encoding/json"
"io"
"net/http"
"github.com/notherealmarco/WASAPhoto/service/database"
"github.com/sirupsen/logrus"
)
func DecodeJsonOrBadRequest(r io.Reader, w http.ResponseWriter, v interface{}, l logrus.FieldLogger) bool {
err := json.NewDecoder(r).Decode(v)
if err != nil {
SendInternalError(err, "Error decoding json", w, l)
return false
}
return true
}
func VerifyUserOrNotFound(db database.AppDatabase, uid string, w http.ResponseWriter) bool {
user_exists, err := db.UserExists(uid)
if err != nil {
SendInternalError(err, "Error verifying user existence", w, nil)
return false
}
if !user_exists {
w.WriteHeader(http.StatusNotFound)
return false
}
return true
}
func SendInternalError(err error, description string, w http.ResponseWriter, l logrus.FieldLogger) {
w.WriteHeader(http.StatusInternalServerError)
l.WithError(err).Error(description)
w.Write([]byte(description)) // todo: maybe in json. But it's not important to send the full error to the client
}

View file

@ -14,7 +14,7 @@ type _reqbody struct {
} }
type _respbody struct { type _respbody struct {
UID string `json:"uid"` UID string `json:"user_id"`
} }
// getContextReply is an example of HTTP endpoint that returns "Hello World!" as a plain text. The signature of this // getContextReply is an example of HTTP endpoint that returns "Hello World!" as a plain text. The signature of this

View file

@ -4,11 +4,36 @@ import (
"net/http" "net/http"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"github.com/notherealmarco/WASAPhoto/service/api/authorization"
"github.com/notherealmarco/WASAPhoto/service/api/helpers"
"github.com/notherealmarco/WASAPhoto/service/api/reqcontext" "github.com/notherealmarco/WASAPhoto/service/api/reqcontext"
"github.com/notherealmarco/WASAPhoto/service/structures"
) )
func (rt *_router) UpdateUsername(w http.ResponseWriter, r *http.Request, ps httprouter.Params, ctx reqcontext.RequestContext) { func (rt *_router) UpdateUsername(w http.ResponseWriter, r *http.Request, ps httprouter.Params, ctx reqcontext.RequestContext) {
auth, err := ctx.Auth.UserAuthorized(rt.db, r.URL.Path // todo: prendere il coso giusto dal path) uid := ps.ByName("user_id")
if !authorization.SendAuthorizationError(ctx.Auth.UserAuthorized, uid, rt.db, w) {
return
}
var req structures.UserDetails
if !helpers.DecodeJsonOrBadRequest(r.Body, w, &req, rt.baseLogger) {
return
}
//err := json.NewDecoder(r.Body).Decode(&req) //todo: capire se serve close
//if err != nil {
// w.WriteHeader(http.StatusBadRequest) // todo: move to DecodeOrBadRequest helper
// return
//}
err := rt.db.UpdateUsername(uid, req.Name)
if err != nil {
w.WriteHeader(http.StatusInternalServerError) // todo: is not ok, maybe let's use a helper
return
}
w.WriteHeader(http.StatusNoContent) // todo: change to 204 also in API spec
} }

View file

@ -12,6 +12,15 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
type AuthStatus int
const (
AUTHORIZED = 0
UNAUTHORIZED = 1
FORBIDDEN = 2
USER_NOT_FOUND = 3
)
// RequestContext is the context of the request, for request-dependent parameters // RequestContext is the context of the request, for request-dependent parameters
type RequestContext struct { type RequestContext struct {
// ReqUUID is the request unique ID // ReqUUID is the request unique ID
@ -26,5 +35,5 @@ type RequestContext struct {
type Authorization interface { type Authorization interface {
GetType() string GetType() string
Authorized(db database.AppDatabase) (bool, error) Authorized(db database.AppDatabase) (bool, error)
UserAuthorized(db database.AppDatabase, uid string) (bool, error) UserAuthorized(db database.AppDatabase, uid string) (AuthStatus, error)
} }

View file

@ -34,14 +34,17 @@ import (
"database/sql" "database/sql"
"errors" "errors"
"fmt" "fmt"
"github.com/notherealmarco/WASAPhoto/service/structures"
) )
// AppDatabase is the high level interface for the DB // AppDatabase is the high level interface for the DB
type AppDatabase interface { type AppDatabase interface {
UserExists(uid string) (bool, error) UserExists(uid string) (bool, error)
GetUserID(name string) (string, error) GetUserID(name string) (string, error)
SetName(name string) error UpdateUsername(uid, name string) error
CreateUser(name string) (string, error) CreateUser(name string) (string, error)
GetUserFollowers(uid string) ([]structures.UIDName, error)
FollowUser(uid string, follow string) error FollowUser(uid string, follow string) error
UnfollowUser(uid string, unfollow string) error UnfollowUser(uid string, unfollow string) error
BanUser(uid string, ban string) error BanUser(uid string, ban string) error

View file

@ -3,6 +3,7 @@ package database
import ( import (
"github.com/gofrs/uuid" "github.com/gofrs/uuid"
"github.com/notherealmarco/WASAPhoto/service/database/db_errors" "github.com/notherealmarco/WASAPhoto/service/database/db_errors"
"github.com/notherealmarco/WASAPhoto/service/structures"
) )
//Check if user exists and if exists return the user id by username //Check if user exists and if exists return the user id by username
@ -11,7 +12,7 @@ import (
// Check if user exists // Check if user exists
func (db *appdbimpl) UserExists(uid string) (bool, error) { func (db *appdbimpl) UserExists(uid string) (bool, error) {
var name string var name string
err := db.c.QueryRow(`SELECT "name" FROM "users" WHERE "uid" = ?`, name).Scan(&name) err := db.c.QueryRow(`SELECT "name" FROM "users" WHERE "uid" = ?`, uid).Scan(&name)
if db_errors.EmptySet(err) { if db_errors.EmptySet(err) {
return false, nil return false, nil
@ -38,6 +39,34 @@ func (db *appdbimpl) CreateUser(name string) (string, error) {
return uid.String(), err return uid.String(), err
} }
// Update username
func (db *appdbimpl) UpdateUsername(uid string, name string) error {
_, err := db.c.Exec(`UPDATE "users" SET "name" = ? WHERE "uid" = ?`, name, uid)
return err
}
// Get user followers
func (db *appdbimpl) GetUserFollowers(uid string) ([]structures.UIDName, error) {
rows, err := db.c.Query(`SELECT "follower", "user.name" FROM "follows", "users"
WHERE "follows.follower" = "users.uid"
AND "followed" = ?`, uid)
if err != nil {
return nil, err
}
var followers []structures.UIDName = make([]structures.UIDName, 0)
for rows.Next() {
var uid string
var name string
err = rows.Scan(&uid, &name)
if err != nil {
return nil, err
}
followers = append(followers, structures.UIDName{UID: uid, Name: name})
}
return followers, nil
}
// Follow a user // Follow a user
func (db *appdbimpl) FollowUser(uid string, follow string) error { func (db *appdbimpl) FollowUser(uid string, follow string) error {
_, err := db.c.Exec(`INSERT INTO "follows" ("follower", "followed") VALUES (?, ?)`, uid, follow) _, err := db.c.Exec(`INSERT INTO "follows" ("follower", "followed") VALUES (?, ?)`, uid, follow)

View file

@ -0,0 +1,10 @@
package structures
type UserDetails struct {
Name string `json:"name"`
}
type UIDName struct {
UID string `json:"user_id"`
Name string `json:"name"`
}