mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-06 00:01:24 +00:00
419 lines
10 KiB
Go
419 lines
10 KiB
Go
package handler
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"github.com/warmbly/warmbly/internal/api/middleware"
|
|
"github.com/warmbly/warmbly/internal/errx"
|
|
"github.com/warmbly/warmbly/internal/models"
|
|
)
|
|
|
|
// Cloud side of the self-hosted warmup pool link.
|
|
|
|
func (h *Handler) poolLinkReady(c *gin.Context) bool {
|
|
if h.PoolLinkService == nil {
|
|
errx.JSON(c, errx.New(errx.NotImplemented, "pool link is not enabled on this instance"))
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// PoolLinkStart opens a handshake for a self-hosted instance.
|
|
func (h *Handler) PoolLinkStart(c *gin.Context) {
|
|
if !h.poolLinkReady(c) {
|
|
return
|
|
}
|
|
var req models.PoolLinkStartRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid request body"))
|
|
return
|
|
}
|
|
res, xerr := h.PoolLinkService.StartCode(c.Request.Context(), req)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.JSON(http.StatusCreated, res)
|
|
}
|
|
|
|
// PoolLinkPoll is polled by the instance until a member approves the code.
|
|
func (h *Handler) PoolLinkPoll(c *gin.Context) {
|
|
if !h.poolLinkReady(c) {
|
|
return
|
|
}
|
|
var req struct {
|
|
DeviceCode string `json:"device_code"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil || req.DeviceCode == "" {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "device_code is required"))
|
|
return
|
|
}
|
|
res, xerr := h.PoolLinkService.PollCode(c.Request.Context(), req.DeviceCode)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, res)
|
|
}
|
|
|
|
// PoolLinkDescribeCode shows the approving member what they are linking.
|
|
func (h *Handler) PoolLinkDescribeCode(c *gin.Context) {
|
|
if !h.poolLinkReady(c) {
|
|
return
|
|
}
|
|
code, xerr := h.PoolLinkService.DescribeCode(c.Request.Context(), c.Param("code"))
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, code)
|
|
}
|
|
|
|
// PoolLinkApproveCode binds the code to the workspace named in the body, not the session's.
|
|
func (h *Handler) PoolLinkApproveCode(c *gin.Context) {
|
|
if !h.poolLinkReady(c) {
|
|
return
|
|
}
|
|
userID, err := middleware.GetUserUUID(c)
|
|
if err != nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
var req struct {
|
|
OrganizationID string `json:"organization_id"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid request body"))
|
|
return
|
|
}
|
|
orgID, err := uuid.Parse(req.OrganizationID)
|
|
if err != nil {
|
|
if sessionOrg := middleware.GetOrganizationID(c); sessionOrg != nil {
|
|
orgID = *sessionOrg
|
|
} else {
|
|
errx.JSON(c, errx.ErrNoOrganization)
|
|
return
|
|
}
|
|
}
|
|
allowed, xerr := h.OrganizationService.HasPermission(c.Request.Context(), orgID, userID, models.PermManageSettings)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
if !allowed {
|
|
errx.JSON(c, errx.New(errx.Forbidden, "manage settings permission is required to link an instance"))
|
|
return
|
|
}
|
|
inst, xerr := h.PoolLinkService.ApproveCode(c.Request.Context(), c.Param("code"), orgID, userID)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
h.AuditService.LogAction(c.Request.Context(), orgID, userID, models.AuditActionConnect, models.AuditEntityPoolLink, &inst.ID,
|
|
c.ClientIP(), c.Request.UserAgent(), nil, map[string]string{"instance_name": inst.Name})
|
|
c.JSON(http.StatusOK, inst)
|
|
}
|
|
|
|
func (h *Handler) PoolLinkDenyCode(c *gin.Context) {
|
|
if !h.poolLinkReady(c) {
|
|
return
|
|
}
|
|
if xerr := h.PoolLinkService.DenyCode(c.Request.Context(), c.Param("code")); xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.Status(http.StatusNoContent)
|
|
}
|
|
|
|
// PoolLinkListInstances lists the workspace's linked instances.
|
|
func (h *Handler) PoolLinkListInstances(c *gin.Context) {
|
|
if !h.poolLinkReady(c) {
|
|
return
|
|
}
|
|
orgID := middleware.GetOrganizationID(c)
|
|
if orgID == nil {
|
|
errx.JSON(c, errx.ErrNoOrganization)
|
|
return
|
|
}
|
|
list, xerr := h.PoolLinkService.ListInstances(c.Request.Context(), *orgID)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
plan, xerr := h.PoolLinkService.Plan(c.Request.Context(), *orgID)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"data": list, "plan": plan})
|
|
}
|
|
|
|
// PoolLinkRevokeInstance ends a link and removes its mailboxes.
|
|
func (h *Handler) PoolLinkRevokeInstance(c *gin.Context) {
|
|
if !h.poolLinkReady(c) {
|
|
return
|
|
}
|
|
orgID := middleware.GetOrganizationID(c)
|
|
if orgID == nil {
|
|
errx.JSON(c, errx.ErrNoOrganization)
|
|
return
|
|
}
|
|
id, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.ErrUuid)
|
|
return
|
|
}
|
|
if xerr := h.PoolLinkService.RevokeInstance(c.Request.Context(), *orgID, id); xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
h.auditOrg(c, models.AuditActionDisconnect, models.AuditEntityPoolLink, &id, nil, nil)
|
|
c.Status(http.StatusNoContent)
|
|
}
|
|
|
|
// Instance-token routes.
|
|
|
|
func (h *Handler) PoolLinkInstanceInfo(c *gin.Context) {
|
|
inst := middleware.GetPoolLinkInstance(c)
|
|
if inst == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
info, xerr := h.PoolLinkService.InstanceInfo(c.Request.Context(), inst)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, info)
|
|
}
|
|
|
|
func (h *Handler) PoolLinkInstanceDisconnect(c *gin.Context) {
|
|
inst := middleware.GetPoolLinkInstance(c)
|
|
if inst == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
if xerr := h.PoolLinkService.RevokeInstance(c.Request.Context(), inst.OrganizationID, inst.ID); xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.Status(http.StatusNoContent)
|
|
}
|
|
|
|
func (h *Handler) PoolLinkInstanceMailboxes(c *gin.Context) {
|
|
inst := middleware.GetPoolLinkInstance(c)
|
|
if inst == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
list, xerr := h.PoolLinkService.ListMailboxes(c.Request.Context(), inst)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, list)
|
|
}
|
|
|
|
func (h *Handler) PoolLinkEnroll(c *gin.Context) {
|
|
inst := middleware.GetPoolLinkInstance(c)
|
|
if inst == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
var req models.PoolLinkEnrollRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid request body"))
|
|
return
|
|
}
|
|
state, xerr := h.PoolLinkService.Enroll(c.Request.Context(), inst, req)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.JSON(http.StatusCreated, state)
|
|
}
|
|
|
|
func poolLinkRemoteID(c *gin.Context) (uuid.UUID, bool) {
|
|
id, err := uuid.Parse(c.Param("remoteId"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.ErrUuid)
|
|
return uuid.Nil, false
|
|
}
|
|
return id, true
|
|
}
|
|
|
|
func (h *Handler) PoolLinkGetMailbox(c *gin.Context) {
|
|
inst := middleware.GetPoolLinkInstance(c)
|
|
if inst == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
id, ok := poolLinkRemoteID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
state, xerr := h.PoolLinkService.GetMailbox(c.Request.Context(), inst, id)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, state)
|
|
}
|
|
|
|
func (h *Handler) PoolLinkPatchMailbox(c *gin.Context) {
|
|
inst := middleware.GetPoolLinkInstance(c)
|
|
if inst == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
id, ok := poolLinkRemoteID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
var patch models.PoolLinkMailboxPatch
|
|
if err := c.ShouldBindJSON(&patch); err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid request body"))
|
|
return
|
|
}
|
|
state, xerr := h.PoolLinkService.PatchMailbox(c.Request.Context(), inst, id, patch)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, state)
|
|
}
|
|
|
|
func (h *Handler) PoolLinkUnenroll(c *gin.Context) {
|
|
inst := middleware.GetPoolLinkInstance(c)
|
|
if inst == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
id, ok := poolLinkRemoteID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
if xerr := h.PoolLinkService.Unenroll(c.Request.Context(), inst, id); xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.Status(http.StatusNoContent)
|
|
}
|
|
|
|
// Cloud-managed mailboxes for a linked instance.
|
|
|
|
func (h *Handler) PoolLinkOAuthStart(c *gin.Context) {
|
|
inst := middleware.GetPoolLinkInstance(c)
|
|
if inst == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
var req models.PoolLinkOAuthStartRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid request body"))
|
|
return
|
|
}
|
|
res, xerr := h.PoolLinkService.StartOAuth(c.Request.Context(), inst, req)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.JSON(http.StatusCreated, res)
|
|
}
|
|
|
|
func (h *Handler) PoolLinkOAuthFinish(c *gin.Context) {
|
|
inst := middleware.GetPoolLinkInstance(c)
|
|
if inst == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
var req models.PoolLinkOAuthFinishRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid request body"))
|
|
return
|
|
}
|
|
state, xerr := h.PoolLinkService.FinishOAuth(c.Request.Context(), inst, req.Session)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.JSON(http.StatusCreated, state)
|
|
}
|
|
|
|
func (h *Handler) PoolLinkAccessToken(c *gin.Context) {
|
|
inst := middleware.GetPoolLinkInstance(c)
|
|
if inst == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
remoteID, ok := poolLinkRemoteID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
tok, xerr := h.PoolLinkService.AccessToken(c.Request.Context(), inst, remoteID)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.Header("Cache-Control", "no-store")
|
|
c.JSON(http.StatusOK, tok)
|
|
}
|
|
|
|
func (h *Handler) PoolLinkWorkspaceMailboxes(c *gin.Context) {
|
|
inst := middleware.GetPoolLinkInstance(c)
|
|
if inst == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
list, xerr := h.PoolLinkService.ListWorkspaceMailboxes(c.Request.Context(), inst)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, list)
|
|
}
|
|
|
|
func (h *Handler) PoolLinkAdopt(c *gin.Context) {
|
|
inst := middleware.GetPoolLinkInstance(c)
|
|
if inst == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
var req models.PoolLinkAdoptRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid request body"))
|
|
return
|
|
}
|
|
state, xerr := h.PoolLinkService.Adopt(c.Request.Context(), inst, req)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.JSON(http.StatusCreated, state)
|
|
}
|
|
|
|
func (h *Handler) PoolLinkVerifyWarmupToken(c *gin.Context) {
|
|
inst := middleware.GetPoolLinkInstance(c)
|
|
if inst == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
remoteID, ok := poolLinkRemoteID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
token, err := uuid.Parse(c.Param("token"))
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{"valid": false})
|
|
return
|
|
}
|
|
valid, xerr := h.PoolLinkService.VerifyWarmupToken(c.Request.Context(), inst, remoteID, token)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"valid": valid})
|
|
}
|