mirror of
https://github.com/sigmasternchen/threadule
synced 2025-03-15 08:09:01 +00:00
basic login works
This commit is contained in:
parent
cdfa816949
commit
82e4d233fe
16 changed files with 136 additions and 19 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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))]))
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
38
backend/internal/presentation/auth.go
Normal file
38
backend/internal/presentation/auth.go
Normal 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(¶m)
|
||||
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)
|
||||
}
|
||||
}
|
10
backend/internal/presentation/dto/login.go
Normal file
10
backend/internal/presentation/dto/login.go
Normal 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"`
|
||||
}
|
10
backend/internal/presentation/dto/status.go
Normal file
10
backend/internal/presentation/dto/status.go
Normal 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"`
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
package presentation
|
||||
|
||||
import "threadule/backend/internal/web"
|
||||
|
||||
func Test(ctx *web.Context) {
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue