mirror of
https://github.com/zoriya/Kyoo.git
synced 2026-03-28 04:17:50 -04:00
Add GET /sessions
This commit is contained in:
parent
a70431b460
commit
bf925d8d70
@ -1,8 +1,6 @@
|
||||
module github.com/zoriya/kyoo/keibi
|
||||
|
||||
go 1.25.0
|
||||
|
||||
toolchain go1.26.0
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
github.com/alexedwards/argon2id v1.0.0
|
||||
@ -81,6 +79,7 @@ require (
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mileusna/useragent v1.3.5
|
||||
github.com/swaggo/files/v2 v2.0.2 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0
|
||||
|
||||
@ -130,6 +130,8 @@ github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLO
|
||||
github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mileusna/useragent v1.3.5 h1:SJM5NzBmh/hO+4LGeATKpaEX9+b4vcGg2qXGLiNGDws=
|
||||
github.com/mileusna/useragent v1.3.5/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
|
||||
@ -346,8 +346,10 @@ func main() {
|
||||
g.POST("/users", h.Register)
|
||||
|
||||
g.POST("/sessions", h.Login)
|
||||
r.GET("/sessions", h.ListMySessions)
|
||||
r.DELETE("/sessions", h.Logout)
|
||||
r.DELETE("/sessions/:id", h.Logout)
|
||||
r.GET("/users/:id/sessions", h.ListUserSessions)
|
||||
|
||||
r.GET("/keys", h.ListApiKey)
|
||||
r.POST("/keys", h.CreateApiKey)
|
||||
|
||||
106
auth/sessions.go
106
auth/sessions.go
@ -5,6 +5,7 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alexedwards/argon2id"
|
||||
@ -12,6 +13,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/mileusna/useragent"
|
||||
"github.com/zoriya/kyoo/keibi/dbc"
|
||||
)
|
||||
|
||||
@ -32,23 +34,30 @@ type SessionWToken struct {
|
||||
}
|
||||
|
||||
func MapSession(ses *dbc.Session) Session {
|
||||
dev := ses.Device
|
||||
if ses.Device != nil {
|
||||
ua := useragent.Parse(*ses.Device)
|
||||
uae := ([]string{ua.Name})
|
||||
if ua.Device != "" {
|
||||
uae = append(uae, ua.Device)
|
||||
}
|
||||
if ua.OS != "" {
|
||||
uae = append(uae, ua.OS)
|
||||
}
|
||||
dev = new(strings.Join(uae, " - "))
|
||||
}
|
||||
return Session{
|
||||
Id: ses.Id,
|
||||
CreatedDate: ses.CreatedDate,
|
||||
LastUsed: ses.LastUsed,
|
||||
Device: ses.Device,
|
||||
Device: dev,
|
||||
}
|
||||
}
|
||||
|
||||
func MapSessionToken(ses *dbc.Session) SessionWToken {
|
||||
return SessionWToken{
|
||||
Session: Session{
|
||||
Id: ses.Id,
|
||||
CreatedDate: ses.CreatedDate,
|
||||
LastUsed: ses.LastUsed,
|
||||
Device: ses.Device,
|
||||
},
|
||||
Token: ses.Token,
|
||||
Session: MapSession(ses),
|
||||
Token: ses.Token,
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,6 +137,87 @@ func (h *Handler) createSession(c *echo.Context, user *User) error {
|
||||
return c.JSON(201, MapSessionToken(&session))
|
||||
}
|
||||
|
||||
// @Summary List my sessions
|
||||
// @Description List all active sessions for the currently connected user
|
||||
// @Tags sessions
|
||||
// @Produce json
|
||||
// @Security Jwt
|
||||
// @Success 200 {array} Session
|
||||
// @Failure 401 {object} KError "Missing jwt token"
|
||||
// @Failure 403 {object} KError "Invalid jwt token (or expired)"
|
||||
// @Router /sessions [get]
|
||||
func (h *Handler) ListMySessions(c *echo.Context) error {
|
||||
ctx := c.Request().Context()
|
||||
uid, err := GetCurrentUserId(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
users, err := h.db.GetUser(ctx, dbc.GetUserParams{
|
||||
UseId: true,
|
||||
Id: uid,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dbSessions, err := h.db.GetUserSessions(ctx, users[0].User.Pk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ret := make([]Session, 0, len(dbSessions))
|
||||
for _, ses := range dbSessions {
|
||||
ret = append(ret, MapSession(&ses))
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, ret)
|
||||
}
|
||||
|
||||
// @Summary List user sessions
|
||||
// @Description List all active sessions for a user. Listing someone else's sessions requires users.read.
|
||||
// @Tags sessions
|
||||
// @Produce json
|
||||
// @Security Jwt
|
||||
// @Param id path string true "The id or username of the user" Example(e05089d6-9179-4b5b-a63e-94dd5fc2a397)
|
||||
// @Success 200 {array} Session
|
||||
// @Failure 401 {object} KError "Missing jwt token"
|
||||
// @Failure 403 {object} KError "Missing permissions: users.read."
|
||||
// @Failure 404 {object} KError "No user found with id or username"
|
||||
// @Router /users/{id}/sessions [get]
|
||||
func (h *Handler) ListUserSessions(c *echo.Context) error {
|
||||
ctx := c.Request().Context()
|
||||
if err := CheckPermissions(c, []string{"users.read"}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id := c.Param("id")
|
||||
uid, err := uuid.Parse(id)
|
||||
users, err := h.db.GetUser(ctx, dbc.GetUserParams{
|
||||
UseId: err == nil,
|
||||
Id: uid,
|
||||
Username: id,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(users) == 0 {
|
||||
return echo.NewHTTPError(http.StatusNotFound, "No user found with id or username")
|
||||
}
|
||||
|
||||
dbSessions, err := h.db.GetUserSessions(ctx, users[0].User.Pk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ret := make([]Session, 0, len(dbSessions))
|
||||
for _, ses := range dbSessions {
|
||||
ret = append(ret, MapSession(&ses))
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, ret)
|
||||
}
|
||||
|
||||
// @Summary Logout
|
||||
// @Description Delete a session and logout
|
||||
// @Tags sessions
|
||||
|
||||
61
auth/tests/sessions.hurl
Normal file
61
auth/tests/sessions.hurl
Normal file
@ -0,0 +1,61 @@
|
||||
# Setup first user
|
||||
POST {{host}}/users
|
||||
{
|
||||
"username": "sessions-user-1",
|
||||
"password": "password-sessions-user-1",
|
||||
"email": "sessions-user-1@zoriya.dev"
|
||||
}
|
||||
HTTP 201
|
||||
[Captures]
|
||||
token1: jsonpath "$.token"
|
||||
|
||||
GET {{host}}/jwt
|
||||
Authorization: Bearer {{token1}}
|
||||
HTTP 200
|
||||
[Captures]
|
||||
jwt1: jsonpath "$.token"
|
||||
|
||||
GET {{host}}/users/me
|
||||
Authorization: Bearer {{jwt1}}
|
||||
HTTP 200
|
||||
[Captures]
|
||||
user1Id: jsonpath "$.id"
|
||||
|
||||
# Can list my own sessions
|
||||
GET {{host}}/sessions
|
||||
Authorization: Bearer {{jwt1}}
|
||||
HTTP 200
|
||||
[Captures]
|
||||
session1Id: jsonpath "$[0].id"
|
||||
|
||||
# Setup second user
|
||||
POST {{host}}/users
|
||||
{
|
||||
"username": "sessions-user-2",
|
||||
"password": "password-sessions-user-2",
|
||||
"email": "sessions-user-2@zoriya.dev"
|
||||
}
|
||||
HTTP 201
|
||||
[Captures]
|
||||
token2: jsonpath "$.token"
|
||||
|
||||
GET {{host}}/jwt
|
||||
Authorization: Bearer {{token2}}
|
||||
HTTP 200
|
||||
[Captures]
|
||||
jwt2: jsonpath "$.token"
|
||||
|
||||
# Cannot list another user's sessions without users.read
|
||||
GET {{host}}/users/{{user1Id}}/sessions
|
||||
Authorization: Bearer {{jwt2}}
|
||||
HTTP 403
|
||||
|
||||
# Cleanup second user
|
||||
DELETE {{host}}/users/me
|
||||
Authorization: Bearer {{jwt2}}
|
||||
HTTP 200
|
||||
|
||||
# Cleanup first user
|
||||
DELETE {{host}}/users/me
|
||||
Authorization: Bearer {{jwt1}}
|
||||
HTTP 200
|
||||
@ -13,8 +13,8 @@ import (
|
||||
)
|
||||
|
||||
func GetCurrentUserId(c *echo.Context) (uuid.UUID, error) {
|
||||
user := c.Get("user").(*jwt.Token)
|
||||
if user == nil {
|
||||
user, ok := c.Get("user").(*jwt.Token)
|
||||
if !ok || user == nil {
|
||||
return uuid.UUID{}, echo.NewHTTPError(401, "Unauthorized")
|
||||
}
|
||||
sub, err := user.Claims.GetSubject()
|
||||
@ -29,8 +29,8 @@ func GetCurrentUserId(c *echo.Context) (uuid.UUID, error) {
|
||||
}
|
||||
|
||||
func GetCurrentSessionId(c *echo.Context) (uuid.UUID, error) {
|
||||
user := c.Get("user").(*jwt.Token)
|
||||
if user == nil {
|
||||
user, ok := c.Get("user").(*jwt.Token)
|
||||
if !ok || user == nil {
|
||||
return uuid.UUID{}, echo.NewHTTPError(401, "Unauthorized")
|
||||
}
|
||||
claims, ok := user.Claims.(jwt.MapClaims)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user