Files
server/api/session.go
2026-04-19 19:22:30 +02:00

139 lines
3.2 KiB
Go

package api
import (
"errors"
"github.com/gin-gonic/gin"
"github.com/gotify/server/v2/auth"
"github.com/gotify/server/v2/auth/password"
"github.com/gotify/server/v2/model"
)
// SessionDatabase is the interface for session-related database access.
type SessionDatabase interface {
GetUserByName(name string) (*model.User, error)
CreateClient(client *model.Client) error
GetClientByToken(token string) (*model.Client, error)
DeleteClientByID(id uint) error
}
// SessionAPI provides handlers for cookie-based session authentication.
type SessionAPI struct {
DB SessionDatabase
NotifyDeleted func(uint, string)
SecureCookie bool
}
// swagger:operation POST /auth/local/login auth localLogin
//
// Authenticate via basic auth and create a session.
//
// ---
// consumes: [application/x-www-form-urlencoded]
// produces: [application/json]
// security:
// - basicAuth: []
// parameters:
// - name: name
// in: formData
// description: the client name to create
// required: true
// type: string
// responses:
// 200:
// description: Ok
// schema:
// $ref: "#/definitions/UserExternal"
// headers:
// Set-Cookie:
// type: string
// description: session cookie
// 401:
// description: Unauthorized
// schema:
// $ref: "#/definitions/Error"
func (a *SessionAPI) Login(ctx *gin.Context) {
name, pass, ok := ctx.Request.BasicAuth()
if !ok {
ctx.AbortWithError(401, errors.New("basic auth required"))
return
}
user, err := a.DB.GetUserByName(name)
if err != nil {
ctx.AbortWithError(500, err)
return
}
if user == nil || !password.ComparePassword(user.Pass, []byte(pass)) {
ctx.AbortWithError(401, errors.New("invalid credentials"))
return
}
clientParams := ClientParams{}
if err := ctx.Bind(&clientParams); err != nil {
return
}
client := model.Client{
Name: clientParams.Name,
Token: auth.GenerateNotExistingToken(generateClientToken, a.clientExists),
UserID: user.ID,
}
if success := successOrAbort(ctx, 500, a.DB.CreateClient(&client)); !success {
return
}
auth.SetCookie(ctx.Writer, client.Token, auth.CookieMaxAge, a.SecureCookie)
ctx.JSON(200, &model.UserExternal{
ID: user.ID,
Name: user.Name,
Admin: user.Admin,
})
}
// swagger:operation POST /auth/logout auth logout
//
// End the current session.
//
// Clears the session cookie and deletes the associated client.
//
// ---
// produces: [application/json]
// security:
// - clientTokenHeader: []
// - clientTokenQuery: []
// - basicAuth: []
// responses:
// 200:
// description: Ok
// headers:
// Set-Cookie:
// type: string
// description: cleared session cookie
// 400:
// description: Bad Request
// schema:
// $ref: "#/definitions/Error"
func (a *SessionAPI) Logout(ctx *gin.Context) {
auth.SetCookie(ctx.Writer, "", -1, a.SecureCookie)
client := auth.GetClient(ctx)
if client == nil {
ctx.AbortWithError(403, errors.New("no client auth provided"))
return
}
a.NotifyDeleted(client.UserID, client.Token)
if success := successOrAbort(ctx, 500, a.DB.DeleteClientByID(client.ID)); !success {
return
}
ctx.Status(200)
}
func (a *SessionAPI) clientExists(token string) bool {
client, _ := a.DB.GetClientByToken(token)
return client != nil
}