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" ) // Self-hosted side of the warmup pool link: Settings > Warmbly Cloud. func (h *Handler) cloudLinkReady(c *gin.Context) bool { if h.CloudLinkService == nil { errx.JSON(c, errx.New(errx.NotImplemented, "cloud link is not enabled on this instance")) return false } return true } func (h *Handler) CloudLinkStatus(c *gin.Context) { if !h.cloudLinkReady(c) { return } st, xerr := h.CloudLinkService.Status(c.Request.Context()) if xerr != nil { errx.JSON(c, xerr) return } c.JSON(http.StatusOK, st) } func (h *Handler) CloudLinkConnectStart(c *gin.Context) { if !h.cloudLinkReady(c) { return } userID, err := middleware.GetUserUUID(c) if err != nil { errx.JSON(c, errx.ErrUnauthorized) return } var req struct { CloudURL string `json:"cloud_url"` } _ = c.ShouldBindJSON(&req) p, xerr := h.CloudLinkService.StartConnect(c.Request.Context(), userID, req.CloudURL) if xerr != nil { errx.JSON(c, xerr) return } c.JSON(http.StatusCreated, p) } func (h *Handler) CloudLinkConnectPoll(c *gin.Context) { if !h.cloudLinkReady(c) { return } userID, err := middleware.GetUserUUID(c) if err != nil { errx.JSON(c, errx.ErrUnauthorized) return } res, xerr := h.CloudLinkService.PollConnect(c.Request.Context(), userID) if xerr != nil { errx.JSON(c, xerr) return } if res.Status == models.PoolLinkCodeApproved && res.Link != nil { h.auditOrg(c, models.AuditActionConnect, models.AuditEntityCloudLink, &res.Link.InstanceID, nil, map[string]string{"organization_name": res.Link.OrganizationName}) } c.JSON(http.StatusOK, res) } func (h *Handler) CloudLinkDisconnect(c *gin.Context) { if !h.cloudLinkReady(c) { return } if xerr := h.CloudLinkService.Disconnect(c.Request.Context()); xerr != nil { errx.JSON(c, xerr) return } h.auditOrg(c, models.AuditActionDisconnect, models.AuditEntityCloudLink, nil, nil, nil) c.Status(http.StatusNoContent) } func (h *Handler) CloudLinkMailboxes(c *gin.Context) { if !h.cloudLinkReady(c) { return } orgID := middleware.GetOrganizationID(c) if orgID == nil { errx.JSON(c, errx.ErrNoOrganization) return } rows, xerr := h.CloudLinkService.ListMailboxes(c.Request.Context(), *orgID) if xerr != nil { errx.JSON(c, xerr) return } c.JSON(http.StatusOK, gin.H{"data": rows}) } func cloudLinkAccountID(c *gin.Context) (uuid.UUID, *uuid.UUID, bool) { orgID := middleware.GetOrganizationID(c) if orgID == nil { errx.JSON(c, errx.ErrNoOrganization) return uuid.Nil, nil, false } id, err := uuid.Parse(c.Param("id")) if err != nil { errx.JSON(c, errx.ErrUuid) return uuid.Nil, nil, false } return id, orgID, true } func (h *Handler) CloudLinkEnroll(c *gin.Context) { if !h.cloudLinkReady(c) { return } id, orgID, ok := cloudLinkAccountID(c) if !ok { return } row, xerr := h.CloudLinkService.Enroll(c.Request.Context(), *orgID, id) if xerr != nil { errx.JSON(c, xerr) return } h.auditOrg(c, models.AuditActionStart, models.AuditEntityCloudLink, &id, nil, map[string]string{"email": row.Email}) c.JSON(http.StatusOK, row) } func (h *Handler) CloudLinkUnenroll(c *gin.Context) { if !h.cloudLinkReady(c) { return } id, orgID, ok := cloudLinkAccountID(c) if !ok { return } if xerr := h.CloudLinkService.Unenroll(c.Request.Context(), *orgID, id); xerr != nil { errx.JSON(c, xerr) return } h.auditOrg(c, models.AuditActionStop, models.AuditEntityCloudLink, &id, nil, nil) c.Status(http.StatusNoContent) } func (h *Handler) CloudLinkPause(c *gin.Context) { h.cloudLinkLifecycle(c, "pause", models.AuditActionPause) } func (h *Handler) CloudLinkResume(c *gin.Context) { h.cloudLinkLifecycle(c, "resume", models.AuditActionResume) } func (h *Handler) cloudLinkLifecycle(c *gin.Context, action string, audit models.AuditAction) { if !h.cloudLinkReady(c) { return } id, orgID, ok := cloudLinkAccountID(c) if !ok { return } row, xerr := h.CloudLinkService.SetLifecycle(c.Request.Context(), *orgID, id, action) if xerr != nil { errx.JSON(c, xerr) return } h.auditOrg(c, audit, models.AuditEntityCloudLink, &id, nil, nil) c.JSON(http.StatusOK, row) } // Cloud-managed mailboxes: sign in through Warmbly Cloud, adopt workspace mailboxes. func (h *Handler) CloudLinkOAuthStart(c *gin.Context) { if !h.cloudLinkReady(c) { return } orgID := middleware.GetOrganizationID(c) userID, err := middleware.GetUserUUID(c) if orgID == nil || err != nil { errx.JSON(c, errx.ErrNoOrganization) return } var req struct { Provider models.InboxProvider `json:"provider"` } if err := c.ShouldBindJSON(&req); err != nil { errx.JSON(c, errx.New(errx.BadRequest, "invalid request body")) return } res, xerr := h.CloudLinkService.StartOAuth(c.Request.Context(), *orgID, userID, req.Provider) if xerr != nil { errx.JSON(c, xerr) return } c.JSON(http.StatusCreated, res) } func (h *Handler) CloudLinkOAuthFinish(c *gin.Context) { if !h.cloudLinkReady(c) { return } orgID := middleware.GetOrganizationID(c) userID, err := middleware.GetUserUUID(c) if orgID == nil || err != nil { errx.JSON(c, errx.ErrNoOrganization) return } var req struct { Session string `json:"session"` } if err := c.ShouldBindJSON(&req); err != nil { errx.JSON(c, errx.New(errx.BadRequest, "invalid request body")) return } acc, xerr := h.CloudLinkService.FinishOAuth(c.Request.Context(), *orgID, userID, req.Session) if xerr != nil { errx.JSON(c, xerr) return } h.auditOrg(c, models.AuditActionConnect, models.AuditEntityEmailAccount, &acc.ID, nil, map[string]string{"email": acc.Email, "via": "warmbly_cloud"}) c.JSON(http.StatusCreated, acc) } func (h *Handler) CloudLinkWorkspaceMailboxes(c *gin.Context) { if !h.cloudLinkReady(c) { return } list, xerr := h.CloudLinkService.ListWorkspaceMailboxes(c.Request.Context()) if xerr != nil { errx.JSON(c, xerr) return } c.JSON(http.StatusOK, gin.H{"data": list}) } func (h *Handler) CloudLinkAdopt(c *gin.Context) { if !h.cloudLinkReady(c) { return } id, orgID, ok := cloudLinkAccountID(c) if !ok { return } userID, err := middleware.GetUserUUID(c) if err != nil { errx.JSON(c, errx.ErrUnauthorized) return } acc, xerr := h.CloudLinkService.Adopt(c.Request.Context(), *orgID, userID, id) if xerr != nil { errx.JSON(c, xerr) return } h.auditOrg(c, models.AuditActionConnect, models.AuditEntityEmailAccount, &acc.ID, nil, map[string]string{"email": acc.Email, "via": "warmbly_cloud"}) c.JSON(http.StatusCreated, acc) } // InternalCloudLinkToken is the worker's credential for a managed mailbox. // // GET /api/v1/internal/cloud-link/token/:id -> 200 {"access_token","expires_at"} | 4xx with the cloud's reason func (h *Handler) InternalCloudLinkToken(c *gin.Context) { if h.CloudLinkService == nil { errx.JSON(c, errx.ErrNotFound) return } id, err := uuid.Parse(c.Param("id")) if err != nil { errx.JSON(c, errx.ErrUuid) return } tok, xerr := h.CloudLinkService.AccessToken(c.Request.Context(), id) if xerr != nil { errx.JSON(c, xerr) return } c.Header("Cache-Control", "no-store") c.JSON(http.StatusOK, tok) }