basic login works

This commit is contained in:
overflowerror 2021-08-14 18:41:12 +02:00
parent cdfa816949
commit 82e4d233fe
16 changed files with 136 additions and 19 deletions

View file

@ -1,6 +1,7 @@
package data
import (
"gorm.io/gorm/clause"
"threadule/backend/internal/data/models"
"time"
)
@ -15,6 +16,8 @@ func (d *Data) CleanupSessions() error {
func (d *Data) GetSession(id string) (*models.Session, error) {
var session models.Session
err := d.db.
Preload("User").
Preload("User.Groups").
Where("valid_until > ?", time.Now()).
Where("id = ?", id).
First(&session).
@ -22,12 +25,14 @@ func (d *Data) GetSession(id string) (*models.Session, error) {
if err != nil {
return nil, err
} else {
session.User.Password = ""
return &session, nil
}
}
func (d *Data) UpdateSession(session *models.Session) error {
return d.db.
Omit(clause.Associations).
Save(session).
Error
}

View file

@ -1,12 +1,14 @@
package data
import (
"gorm.io/gorm/clause"
"threadule/backend/internal/data/models"
"time"
)
func (d *Data) UpdateThread(thread *models.Thread) error {
return d.db.
Omit(clause.Associations).
Save(thread).
Error
}

View file

@ -1,9 +1,13 @@
package data
import "threadule/backend/internal/data/models"
import (
"gorm.io/gorm/clause"
"threadule/backend/internal/data/models"
)
func (d *Data) UpdateTweet(tweet *models.Tweet) error {
return d.db.
Omit(clause.Associations).
Save(tweet).
Error
}

View file

@ -1,6 +1,9 @@
package data
import "threadule/backend/internal/data/models"
import (
"gorm.io/gorm/clause"
"threadule/backend/internal/data/models"
)
func (d *Data) CountUsers() (int64, error) {
var c int64
@ -13,6 +16,7 @@ func (d *Data) CountUsers() (int64, error) {
func (d *Data) CreateUser(user *models.User) error {
return d.db.
Omit(clause.Associations).
Create(user).
Error
}

View file

@ -40,11 +40,11 @@ func (l *Logic) Login(username, password string) (string, error) {
user, err := l.ctx.Data.GetUserByUsername(username)
// the following few lines should prevent timing attacks
hash := "something"
hash := ""
if err == nil {
hash = user.Password
} else {
password = "something else"
password = "something"
}
if l.checkPassword(hash, password) && user != nil {

View file

@ -4,6 +4,7 @@ import (
"golang.org/x/crypto/bcrypt"
"math/rand"
"strings"
"time"
)
const defaultPasswordLength = 16
@ -14,6 +15,7 @@ const defaultPasswordCharSet = "abcdefghijklmnopqrstuvwxyz" +
func (l *Logic) defaultPassword() string {
builder := strings.Builder{}
rand.Seed(time.Now().Unix())
for i := 0; i < defaultPasswordLength; i++ {
builder.WriteRune(rune(defaultPasswordCharSet[rand.Intn(len(defaultPasswordCharSet))]))
}

View file

@ -46,6 +46,7 @@ func (l *Logic) firstTimeSetup() error {
adminUser.Groups = []*models.Group{adminGroup}
password := l.defaultPassword()
adminUser.Password, err = l.hashPassword(password)
if err != nil {
// if this fails we can't recover anyway
l.ctx.Log.Fatal(err)

View file

@ -0,0 +1,38 @@
package presentation
import (
"fmt"
"net/http"
"threadule/backend/internal/presentation/dto"
"threadule/backend/internal/web"
)
func Login(ctx *web.Context) {
var param dto.LoginParams
err := ctx.ReadJSON(&param)
if err != nil {
StatusResponse(ctx, http.StatusBadRequest, err.Error())
return
}
sessionToken, err := ctx.AppCtx.Logic.Login(param.Username, param.Password)
if err != nil {
ErrorResponse(ctx, err)
return
}
err = ctx.WriteJSON(&dto.LoginResponse{
Token: sessionToken,
})
if err != nil {
ErrorResponse(ctx, err)
}
}
func GetAuthenticationData(ctx *web.Context) {
fmt.Println(ctx.Session.User)
err := ctx.WriteJSON(ctx.Session.User)
if err != nil {
ErrorResponse(ctx, err)
}
}

View file

@ -0,0 +1,10 @@
package dto
type LoginParams struct {
Username string `json:"username"`
Password string `json:"password"`
}
type LoginResponse struct {
Token string `json:"token"`
}

View file

@ -0,0 +1,10 @@
package dto
import "time"
type Status struct {
Status int `json:"status"`
Message string `json:"message"`
Details string `json:"details"`
Time time.Time `json:"time"`
}

View file

@ -2,10 +2,35 @@ package presentation
import (
"net/http"
"threadule/backend/internal/logic"
"threadule/backend/internal/presentation/dto"
"threadule/backend/internal/web"
"time"
)
func GenericStatusResponse(ctx *web.Context, status int) {
func StatusResponse(ctx *web.Context, status int, details string) {
ctx.Response.WriteHeader(status)
_, _ = ctx.Response.Write([]byte(http.StatusText(status)))
err := ctx.WriteJSON(&dto.Status{
Status: status,
Message: http.StatusText(status),
Details: details,
Time: time.Now(),
})
if err != nil {
_, _ = ctx.Response.Write([]byte("something went very wrong"))
}
}
func ErrorResponse(ctx *web.Context, err error) {
switch err {
case logic.ErrLoginFailed:
StatusResponse(ctx, http.StatusForbidden, err.Error())
case logic.ErrInvalidSession:
StatusResponse(ctx, http.StatusUnauthorized, err.Error())
case logic.ErrInternalError:
StatusResponse(ctx, http.StatusInternalServerError, err.Error())
default:
StatusResponse(ctx, http.StatusInternalServerError, err.Error())
}
}

View file

@ -1,7 +0,0 @@
package presentation
import "threadule/backend/internal/web"
func Test(ctx *web.Context) {
}

View file

@ -3,7 +3,7 @@ package router
import (
"net/http"
"strings"
"threadule/backend/internal/presentation"
. "threadule/backend/internal/presentation"
"threadule/backend/internal/web"
)
@ -13,14 +13,14 @@ func authenticated(next web.Handler) web.Handler {
return func(ctx *web.Context) {
authHeader := ctx.Request.Header.Get("Authentication")
if !strings.HasPrefix(authHeader, authPrefix) {
presentation.GenericStatusResponse(ctx, http.StatusUnauthorized)
StatusResponse(ctx, http.StatusBadRequest, "Authentication header missing or malformed")
return
}
authHeader = strings.TrimPrefix(authHeader, authPrefix)
user, err := ctx.AppCtx.Logic.AuthenticateSession(authHeader)
if err != nil {
presentation.GenericStatusResponse(ctx, http.StatusUnauthorized)
StatusResponse(ctx, http.StatusUnauthorized, err.Error())
return
}

View file

@ -10,6 +10,7 @@ import (
func ctxWrapper(appCtx *app.Context, handler web.Handler) httprouter.Handle {
return func(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
writer.Header().Add("Content-Type", "application/json")
start := time.Now()
handler(&web.Context{
Response: writer,
@ -18,7 +19,7 @@ func ctxWrapper(appCtx *app.Context, handler web.Handler) httprouter.Handle {
AppCtx: appCtx,
})
end := time.Now()
appCtx.AccessLog.Printf("%s %s; %lld ms", request.Method, request.URL.String(), end.Sub(start).Milliseconds())
appCtx.AccessLog.Printf("%s %s; %d ms", request.Method, request.URL.String(), end.Sub(start).Milliseconds())
}
}

View file

@ -4,13 +4,14 @@ import (
"github.com/julienschmidt/httprouter"
"net/http"
"threadule/backend/internal/app"
"threadule/backend/internal/presentation"
. "threadule/backend/internal/presentation"
)
func Setup(ctx *app.Context) http.Handler {
router := &router{Router: httprouter.New(), appCtx: ctx}
router.GET("/", presentation.Test)
router.POST("/authentication", Login)
router.GET("/authentication", authenticated(GetAuthenticationData))
return router
}

View file

@ -1,7 +1,9 @@
package web
import (
"encoding/json"
"github.com/julienschmidt/httprouter"
"io/ioutil"
"net/http"
"threadule/backend/internal/app"
"threadule/backend/internal/data/models"
@ -18,3 +20,22 @@ type Context struct {
AppCtx *app.Context
Session SessionInfo
}
func (c *Context) ReadJSON(v interface{}) error {
content, err := ioutil.ReadAll(c.Request.Body)
if err != nil {
return err
}
return json.Unmarshal(content, v)
}
func (c *Context) WriteJSON(v interface{}) error {
content, err := json.Marshal(v)
if err != nil {
return err
}
_, err = c.Response.Write(content)
return err
}