mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-09-18 00:49:04 +02:00
135 lines
4.1 KiB
Go
135 lines
4.1 KiB
Go
package devicelogin
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/pocket-id/pocket-id/backend/internal/dto"
|
|
"github.com/pocket-id/pocket-id/backend/internal/utils/cookie"
|
|
)
|
|
|
|
type handler struct {
|
|
service *Service
|
|
baseURL string
|
|
appConfig AppConfigProvider
|
|
}
|
|
|
|
func newHandler(service *Service, baseURL string, appConfig AppConfigProvider) *handler {
|
|
return &handler{
|
|
service: service,
|
|
baseURL: baseURL,
|
|
appConfig: appConfig,
|
|
}
|
|
}
|
|
|
|
// createRequest godoc
|
|
// @Summary Create device login request
|
|
// @Description Create a short-lived request that can be approved from another authenticated device
|
|
// @Tags Device Login
|
|
// @Produce json
|
|
// @Success 201 {object} requestCreateDto "Created device login request"
|
|
// @Router /api/device-login/requests [post]
|
|
func (h *handler) createRequest(c *gin.Context) {
|
|
request, deviceToken, err := h.service.Create(c.Request.Context(), c.ClientIP(), c.Request.UserAgent())
|
|
if err != nil {
|
|
_ = c.Error(err)
|
|
return
|
|
}
|
|
|
|
verificationURI := h.baseURL + "/device"
|
|
verificationURIComplete := verificationURI + "?user_code=" + url.QueryEscape(request.Code)
|
|
cookie.AddDeviceLoginTokenCookie(c, request.ID, deviceToken)
|
|
c.JSON(http.StatusCreated, requestCreateDto{
|
|
ID: request.ID,
|
|
UserCode: request.Code,
|
|
VerificationURI: verificationURI,
|
|
VerificationURIComplete: verificationURIComplete,
|
|
ExpiresAt: request.ExpiresAt,
|
|
Interval: PollingInterval,
|
|
})
|
|
}
|
|
|
|
// exchangeRequest godoc
|
|
// @Summary Exchange device login request
|
|
// @Description Poll a device login request and create a browser session after it has been approved
|
|
// @Tags Device Login
|
|
// @Produce json
|
|
// @Param id path string true "Device login request ID"
|
|
// @Success 200 {object} dto.UserDto "Approved request exchanged for a user session"
|
|
// @Success 202 "Authorization pending"
|
|
// @Router /api/device-login/requests/{id}/exchange [post]
|
|
func (h *handler) exchangeRequest(c *gin.Context) {
|
|
requestID := c.Param("id")
|
|
deviceToken, _ := c.Cookie(cookie.DeviceLoginTokenCookieName)
|
|
user, accessToken, status, err := h.service.Exchange(c.Request.Context(), requestID, deviceToken, c.ClientIP(), c.Request.UserAgent())
|
|
if err != nil {
|
|
_ = c.Error(err)
|
|
return
|
|
}
|
|
if status == RequestStatusPending {
|
|
c.Status(http.StatusAccepted)
|
|
return
|
|
}
|
|
|
|
var userDto dto.UserDto
|
|
if err = dto.MapStruct(user, &userDto); err != nil {
|
|
_ = c.Error(err)
|
|
return
|
|
}
|
|
|
|
maxAge := int(h.appConfig.GetDbConfig().SessionDuration.AsDurationMinutes().Seconds())
|
|
cookie.AddAccessTokenCookie(c, maxAge, accessToken)
|
|
c.JSON(http.StatusOK, userDto)
|
|
}
|
|
|
|
// inspectRequest godoc
|
|
// @Summary Inspect device login request
|
|
// @Description Retrieve the requesting device details for an authenticated user before approval or denial
|
|
// @Tags Device Login
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param request body verificationDto true "Device login code"
|
|
// @Success 200 {object} verificationInfoDto "Device login request details"
|
|
// @Router /api/device-login/verification [post]
|
|
func (h *handler) inspectRequest(c *gin.Context) {
|
|
var input verificationDto
|
|
if err := c.ShouldBindJSON(&input); err != nil {
|
|
_ = c.Error(err)
|
|
return
|
|
}
|
|
|
|
info, err := h.service.Inspect(c.Request.Context(), input.Code)
|
|
if err != nil {
|
|
_ = c.Error(err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, verificationInfoDto(info))
|
|
}
|
|
|
|
// decideRequest godoc
|
|
// @Summary Decide device login request
|
|
// @Description Approve or deny a device login request; approval requires fresh passkey reauthentication
|
|
// @Tags Device Login
|
|
// @Accept json
|
|
// @Param decision body decisionDto true "Device login decision"
|
|
// @Success 204 "No Content"
|
|
// @Router /api/device-login/verification/decision [post]
|
|
func (h *handler) decideRequest(c *gin.Context) {
|
|
var input decisionDto
|
|
if err := c.ShouldBindJSON(&input); err != nil {
|
|
_ = c.Error(err)
|
|
return
|
|
}
|
|
|
|
reauthenticationToken, _ := c.Cookie(cookie.ReauthenticationTokenCookieName)
|
|
if err := h.service.Decide(c.Request.Context(), input.Code, input.Decision, c.GetString("userID"), reauthenticationToken); err != nil {
|
|
_ = c.Error(err)
|
|
return
|
|
}
|
|
|
|
c.Status(http.StatusNoContent)
|
|
}
|