shiori/internal/http/routes/api/v1/auth.go
Felipe Martin 6f19c12c95
Start working on new REST API. Refactor logic in domains. (#497)
* added 404 template

* added auth domain

* added embed file for frontend

* added base config and dependencies

* added basic new http server

* added separated server command

* updated go modules

* removed modd file

* Added shortcut to send internal server error response

* Added JWT support to Auth Domain

* Added JWT support to API

* docs: added comments to response struct

* naming

* inline returns

* updated dependencies

* production logger

* bookmarks endpoint

* reverted old views api path

* frontend for api v1

* proper 404 error (not working atm)

* use response

* removed 404 html

* server error handler

* login and basic auth

* adjusted session duration

* properly retrieve tags

* properly delete bookmark

* cleanup

* archiver domain

* debug routes

* bookmark routes

* expiration by parameter

* move to logrus

* logout

* frontend cache

* updated dependencies

* http: migrated to gin

* linted

* Added version command

* unit tests, docs

* response test utils and tests

* remove logout handler

* auth

* createtag

* improved http test utilities

* assert message equals

* Remove 1.19 from test matrix

* moved api to v1 folder

* docs: contribute docs

* updated makefile

* updated usage docs

* warn in server command

* updaed docs with shiori version command

* Updated documentation

* deps: update
2023-07-17 14:30:18 +01:00

155 lines
4.2 KiB
Go

package api_v1
import (
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/go-shiori/shiori/internal/config"
"github.com/go-shiori/shiori/internal/http/context"
"github.com/go-shiori/shiori/internal/http/response"
"github.com/go-shiori/shiori/internal/model"
"github.com/sirupsen/logrus"
)
type AuthAPIRoutes struct {
logger *logrus.Logger
deps *config.Dependencies
}
func (r *AuthAPIRoutes) Setup(group *gin.RouterGroup) model.Routes {
group.GET("/me", r.meHandler)
group.POST("/login", r.loginHandler)
group.POST("/refresh", r.refreshHandler)
return r
}
func (r *AuthAPIRoutes) setCookie(c *gin.Context, token string, expiration time.Time) {
c.SetCookie("auth", token, int(expiration.Unix()), "/", "", !r.deps.Config.Development, false)
}
type loginRequestPayload struct {
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"required"`
RememberMe bool `json:"remember_me"`
}
func (p *loginRequestPayload) IsValid() error {
if p.Username == "" {
return fmt.Errorf("username should not be empty")
}
if p.Password == "" {
return fmt.Errorf("password should not be empty")
}
return nil
}
type loginResponseMessage struct {
Token string `json:"token"`
}
// loginHandler godoc
// @Summary Login to an account using username and password
// @Tags Auth
// @Accept json
// @Produce json
// @Param payload body loginRequestPayload false "Login data"
// @Success 200 {object} loginResponseMessage "Login successful"
// @Failure 400 {object} nil "Invalid login data"
// @Router /api/v1/auth/login [post]
func (r *AuthAPIRoutes) loginHandler(c *gin.Context) {
var payload loginRequestPayload
if err := c.ShouldBindJSON(&payload); err != nil {
response.SendInternalServerError(c)
return
}
if err := payload.IsValid(); err != nil {
response.SendError(c, http.StatusBadRequest, err.Error())
return
}
account, err := r.deps.Domains.Auth.GetAccountFromCredentials(c, payload.Username, payload.Password)
if err != nil {
response.SendError(c, http.StatusBadRequest, err.Error())
return
}
expiration := time.Now().Add(time.Hour)
if payload.RememberMe {
expiration = time.Now().Add(time.Hour * 24 * 30)
}
token, err := r.deps.Domains.Auth.CreateTokenForAccount(account, expiration)
if err != nil {
response.SendInternalServerError(c)
return
}
responseMessage := loginResponseMessage{
Token: token,
}
// TODO: move cookie logic to frontend routes
r.setCookie(c, token, expiration)
response.Send(c, http.StatusOK, responseMessage)
}
// refreshHandler godoc
// @Summary Refresh a token for an account
// @Tags Auth
// @securityDefinitions.apikey ApiKeyAuth
// @Produce json
// @Success 200 {object} loginResponseMessage "Refresh successful"
// @Failure 403 {object} nil "Token not provided/invalid"
// @Router /api/v1/auth/refresh [post]
func (r *AuthAPIRoutes) refreshHandler(c *gin.Context) {
ctx := context.NewContextFromGin(c)
if !ctx.UserIsLogged() {
response.SendError(c, http.StatusForbidden, nil)
return
}
expiration := time.Now().Add(time.Hour * 72)
account, _ := c.Get(model.ContextAccountKey)
token, err := r.deps.Domains.Auth.CreateTokenForAccount(account.(*model.Account), expiration)
if err != nil {
response.SendInternalServerError(c)
return
}
responseMessage := loginResponseMessage{
Token: token,
}
r.setCookie(c, token, expiration)
response.Send(c, http.StatusAccepted, responseMessage)
}
// meHandler godoc
// @Summary Get information for the current logged in user
// @Tags Auth
// @securityDefinitions.apikey ApiKeyAuth
// @Produce json
// @Success 200 {object} model.Account
// @Failure 403 {object} nil "Token not provided/invalid"
// @Router /api/v1/auth/me [get]
func (r *AuthAPIRoutes) meHandler(c *gin.Context) {
ctx := context.NewContextFromGin(c)
if !ctx.UserIsLogged() {
response.SendError(c, http.StatusForbidden, nil)
return
}
response.Send(c, http.StatusOK, ctx.GetAccount())
}
func NewAuthAPIRoutes(logger *logrus.Logger, deps *config.Dependencies) *AuthAPIRoutes {
return &AuthAPIRoutes{
logger: logger,
deps: deps,
}
}