mirror of
				https://github.com/zoriya/Kyoo.git
				synced 2025-11-03 19:17:16 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			158 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			158 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package main
 | 
						|
 | 
						|
import (
 | 
						|
	"cmp"
 | 
						|
	"context"
 | 
						|
	"crypto/rand"
 | 
						|
	"encoding/base64"
 | 
						|
	"net/http"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/alexedwards/argon2id"
 | 
						|
	"github.com/golang-jwt/jwt/v5"
 | 
						|
	"github.com/google/uuid"
 | 
						|
	"github.com/jackc/pgx/v5"
 | 
						|
	"github.com/labstack/echo/v4"
 | 
						|
	"github.com/zoriya/kyoo/keibi/dbc"
 | 
						|
)
 | 
						|
 | 
						|
type Session struct {
 | 
						|
	// Unique id of this session. Can be used for calls to DELETE
 | 
						|
	Id uuid.UUID `json:"id"`
 | 
						|
	// When was the session first opened
 | 
						|
	CreatedDate time.Time `json:"createdDate"`
 | 
						|
	// Last date this session was used to access a service.
 | 
						|
	LastUsed time.Time `json:"lastUsed"`
 | 
						|
	// Device that created the session.
 | 
						|
	Device *string `json:"device"`
 | 
						|
}
 | 
						|
 | 
						|
func MapSession(ses *dbc.Session) Session {
 | 
						|
	return Session{
 | 
						|
		Id:          ses.Id,
 | 
						|
		CreatedDate: ses.CreatedDate,
 | 
						|
		LastUsed:    ses.LastUsed,
 | 
						|
		Device:      ses.Device,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type LoginDto struct {
 | 
						|
	// Either the email or the username.
 | 
						|
	Login string `json:"login" validate:"required"`
 | 
						|
	// Password of the account.
 | 
						|
	Password string `json:"password" validate:"required"`
 | 
						|
}
 | 
						|
 | 
						|
// @Summary      Login
 | 
						|
// @Description  Login to your account and open a session
 | 
						|
// @Tags         sessions
 | 
						|
// @Accept       json
 | 
						|
// @Produce      json
 | 
						|
// @Param        device  query   string    false  "The device the created session will be used on"
 | 
						|
// @Param        login   body    LoginDto  false  "Account informations"
 | 
						|
// @Success      201  {object}   dbc.Session
 | 
						|
// @Failure      400  {object}   problem.Problem "Invalid login body"
 | 
						|
// @Failure      403  {object}   problem.Problem "Invalid password"
 | 
						|
// @Failure      404  {object}   problem.Problem "Account does not exists"
 | 
						|
// @Failure      422  {object}   problem.Problem "User does not have a password (registered via oidc, please login via oidc)"
 | 
						|
// @Router /sessions [post]
 | 
						|
func (h *Handler) Login(c echo.Context) error {
 | 
						|
	var req LoginDto
 | 
						|
	err := c.Bind(&req)
 | 
						|
	if err != nil {
 | 
						|
		return echo.NewHTTPError(http.StatusBadRequest, err.Error())
 | 
						|
	}
 | 
						|
	if err = c.Validate(&req); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	dbuser, err := h.db.GetUserByLogin(context.Background(), req.Login)
 | 
						|
	if err != nil {
 | 
						|
		return echo.NewHTTPError(http.StatusNotFound, "No account exists with the specified email or username.")
 | 
						|
	}
 | 
						|
	if dbuser.Password == nil {
 | 
						|
		return echo.NewHTTPError(http.StatusUnprocessableEntity, "Can't login with password, this account was created with OIDC.")
 | 
						|
	}
 | 
						|
 | 
						|
	match, err := argon2id.ComparePasswordAndHash(req.Password, *dbuser.Password)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if !match {
 | 
						|
		return echo.NewHTTPError(http.StatusForbidden, "Invalid password")
 | 
						|
	}
 | 
						|
 | 
						|
	user := MapDbUser(&dbuser)
 | 
						|
	return h.createSession(c, &user)
 | 
						|
}
 | 
						|
 | 
						|
func (h *Handler) createSession(c echo.Context, user *User) error {
 | 
						|
	ctx := context.Background()
 | 
						|
 | 
						|
	id := make([]byte, 64)
 | 
						|
	_, err := rand.Read(id)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	dev := cmp.Or(c.Param("device"), c.Request().Header.Get("User-Agent"))
 | 
						|
	device := &dev
 | 
						|
	if dev == "" {
 | 
						|
		device = nil
 | 
						|
	}
 | 
						|
 | 
						|
	session, err := h.db.CreateSession(ctx, dbc.CreateSessionParams{
 | 
						|
		Token:  base64.StdEncoding.EncodeToString(id),
 | 
						|
		UserPk: user.Pk,
 | 
						|
		Device: device,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	return c.JSON(201, session)
 | 
						|
}
 | 
						|
 | 
						|
// @Summary      Logout
 | 
						|
// @Description  Delete a session and logout
 | 
						|
// @Tags         sessions
 | 
						|
// @Produce      json
 | 
						|
// @Security     Jwt
 | 
						|
// @Param        id   path      string    true  "The id of the session to delete" Format(uuid)
 | 
						|
// @Success      200  {object}  Session
 | 
						|
// @Failure      400  {object}  problem.Problem "Invalid session id"
 | 
						|
// @Failure      401  {object}  problem.Problem "Missing jwt token"
 | 
						|
// @Failure      403  {object}  problem.Problem "Invalid jwt token (or expired)"
 | 
						|
// @Failure      404  {object}  problem.Problem "Session not found with specified id (if not using the /current route)"
 | 
						|
// @Router /sessions/{id} [delete]
 | 
						|
// @Router /sessions/current [delete]
 | 
						|
func (h *Handler) Logout(c echo.Context) error {
 | 
						|
	uid, err := GetCurrentUserId(c)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	session := c.Param("id")
 | 
						|
	if session == "current" {
 | 
						|
		sid, ok := c.Get("user").(*jwt.Token).Claims.(jwt.MapClaims)["sid"]
 | 
						|
		if !ok {
 | 
						|
			return echo.NewHTTPError(400, "Missing session id")
 | 
						|
		}
 | 
						|
		session = sid.(string)
 | 
						|
	}
 | 
						|
	sid, err := uuid.Parse(session)
 | 
						|
	if err != nil {
 | 
						|
		return echo.NewHTTPError(400, "Invalid session id")
 | 
						|
	}
 | 
						|
 | 
						|
	ret, err := h.db.DeleteSession(context.Background(), dbc.DeleteSessionParams{
 | 
						|
		Id:     sid,
 | 
						|
		UserId: uid,
 | 
						|
	})
 | 
						|
	if err == pgx.ErrNoRows {
 | 
						|
		return echo.NewHTTPError(404, "Session not found with specified id")
 | 
						|
	} else if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	return c.JSON(200, MapSession(&ret))
 | 
						|
}
 |