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 {
rt.baseLogger.WithError(err).Info("User not authorized")
w.WriteHeader(http.StatusUnauthorized)
return
}

View file

@ -8,6 +8,11 @@ import (
func (rt *_router) Handler() http.Handler {
// Register routes
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("/context", rt.wrap(rt.getContextReply))

View file

@ -4,6 +4,7 @@ import (
"errors"
"strings"
"github.com/notherealmarco/WASAPhoto/service/api/reqcontext"
"github.com/notherealmarco/WASAPhoto/service/database"
)
@ -32,19 +33,40 @@ func (b *BearerAuth) GetToken() string {
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
state, err := db.UserExists(b.token)
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) (bool, error) {
if b.token == uid {
return b.Authorized(db)
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
}
return false, nil
if !user_exists {
return reqcontext.USER_NOT_FOUND, nil
}
if b.token == uid {
auth, err := b.Authorized(db)
if err != nil {
return -1, err
}
return auth, nil
}
return reqcontext.FORBIDDEN, nil
}

View file

@ -2,8 +2,10 @@ package authorization
import (
"errors"
"net/http"
"github.com/notherealmarco/WASAPhoto/service/api/reqcontext"
"github.com/notherealmarco/WASAPhoto/service/database"
)
func BuildAuth(header string) (reqcontext.Authorization, error) {
@ -16,3 +18,26 @@ func BuildAuth(header string) (reqcontext.Authorization, error) {
}
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 {
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

View file

@ -4,11 +4,36 @@ import (
"net/http"
"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/structures"
)
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"
)
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
type RequestContext struct {
// ReqUUID is the request unique ID
@ -26,5 +35,5 @@ type RequestContext struct {
type Authorization interface {
GetType() string
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"
"errors"
"fmt"
"github.com/notherealmarco/WASAPhoto/service/structures"
)
// AppDatabase is the high level interface for the DB
type AppDatabase interface {
UserExists(uid string) (bool, error)
GetUserID(name string) (string, error)
SetName(name string) error
UpdateUsername(uid, name string) error
CreateUser(name string) (string, error)
GetUserFollowers(uid string) ([]structures.UIDName, error)
FollowUser(uid string, follow string) error
UnfollowUser(uid string, unfollow string) error
BanUser(uid string, ban string) error

View file

@ -3,6 +3,7 @@ package database
import (
"github.com/gofrs/uuid"
"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
@ -11,7 +12,7 @@ import (
// Check if user exists
func (db *appdbimpl) UserExists(uid string) (bool, error) {
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) {
return false, nil
@ -38,6 +39,34 @@ func (db *appdbimpl) CreateUser(name string) (string, error) {
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
func (db *appdbimpl) FollowUser(uid string, follow string) error {
_, 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"`
}