diff --git a/cmd/conversation.go b/cmd/conversation.go index f4b12a1..db7cd68 100644 --- a/cmd/conversation.go +++ b/cmd/conversation.go @@ -131,6 +131,8 @@ func handleUpdateConversationUserAssignee(r *fastglue.Request) error { if err := app.conversation.UpdateConversationUserAssignee(uuid, assigneeID, user); err != nil { return sendErrorEnvelope(r, err) } + + app.automation.EvaluateConversationUpdateRules(uuid) return r.SendEnvelope(true) } @@ -152,6 +154,8 @@ func handleUpdateTeamAssignee(r *fastglue.Request) error { if err := app.conversation.UpdateConversationTeamAssignee(uuid, assigneeID, user); err != nil { return sendErrorEnvelope(r, err) } + + app.automation.EvaluateConversationUpdateRules(uuid) return r.SendEnvelope(true) } @@ -171,6 +175,8 @@ func handleUpdateConversationPriority(r *fastglue.Request) error { if err := app.conversation.UpdateConversationPriority(uuid, priority, user); err != nil { return sendErrorEnvelope(r, err) } + + app.automation.EvaluateConversationUpdateRules(uuid) return r.SendEnvelope(true) } @@ -190,6 +196,8 @@ func handleUpdateConversationStatus(r *fastglue.Request) error { if err := app.conversation.UpdateConversationStatus(uuid, status, user); err != nil { return sendErrorEnvelope(r, err) } + + app.automation.EvaluateConversationUpdateRules(uuid) return r.SendEnvelope(true) } @@ -219,10 +227,10 @@ func handleAddConversationTags(r *fastglue.Request) error { if err := app.conversation.UpsertConversationTags(uuid, tagIDs); err != nil { return sendErrorEnvelope(r, err) } + app.automation.EvaluateConversationUpdateRules(uuid) return r.SendEnvelope(true) } - // handleDashboardCounts retrieves general dashboard counts for all users. func handleDashboardCounts(r *fastglue.Request) error { var ( diff --git a/cmd/handlers.go b/cmd/handlers.go index 8a8ee91..78a4b17 100644 --- a/cmd/handlers.go +++ b/cmd/handlers.go @@ -15,136 +15,134 @@ import ( func initHandlers(g *fastglue.Fastglue, hub *ws.Hub) { // Authentication. g.POST("/api/login", handleLogin) - g.GET("/api/logout", sess(authSess((handleLogout)))) + g.GET("/api/logout", authMiddleware(handleLogout, "", "")) g.GET("/api/oidc/{id}/login", handleOIDCLogin) g.GET("/api/oidc/finish", handleOIDCCallback) // Health check. g.GET("/health", handleHealthCheck) - // Serve uploaded files. - g.GET("/uploads/{uuid}", sess(authSess(handleServeUploadedFiles))) + // Serve media files. + g.GET("/uploads/{uuid}", authMiddleware(handleServeMedia, "", "")) // Settings. g.GET("/api/settings/general", handleGetGeneralSettings) - g.PUT("/api/settings/general", perm(handleUpdateGeneralSettings, "settings_general", "write")) - g.GET("/api/settings/notifications/email", perm(handleGetEmailNotificationSettings, "settings_notifications", "read")) - g.PUT("/api/settings/notifications/email", perm(handleUpdateEmailNotificationSettings, "settings_notifications", "write")) + g.PUT("/api/settings/general", authMiddleware(handleUpdateGeneralSettings, "settings_general", "write")) + g.GET("/api/settings/notifications/email", authMiddleware(handleGetEmailNotificationSettings, "settings_notifications", "read")) + g.PUT("/api/settings/notifications/email", authMiddleware(handleUpdateEmailNotificationSettings, "settings_notifications", "write")) // OpenID SSO. g.GET("/api/oidc", handleGetAllOIDC) - g.GET("/api/oidc/{id}", perm(handleGetOIDC, "oidc", "read")) - g.POST("/api/oidc", perm(handleCreateOIDC, "oidc", "write")) - g.PUT("/api/oidc/{id}", perm(handleUpdateOIDC, "oidc", "write")) - g.DELETE("/api/oidc/{id}", perm(handleDeleteOIDC, "oidc", "delete")) + g.GET("/api/oidc/{id}", authMiddleware(handleGetOIDC, "oidc", "read")) + g.POST("/api/oidc", authMiddleware(handleCreateOIDC, "oidc", "write")) + g.PUT("/api/oidc/{id}", authMiddleware(handleUpdateOIDC, "oidc", "write")) + g.DELETE("/api/oidc/{id}", authMiddleware(handleDeleteOIDC, "oidc", "delete")) // Conversation and message. - g.GET("/api/conversations/all", perm(handleGetAllConversations, "conversations", "read_all")) - g.GET("/api/conversations/unassigned", perm(handleGetUnassignedConversations, "conversations", "read_unassigned")) - g.GET("/api/conversations/assigned", perm(handleGetAssignedConversations, "conversations", "read_assigned")) - - g.PUT("/api/conversations/{uuid}/assignee/user", perm(handleUpdateConversationUserAssignee, "conversations", "update_user_assignee")) - g.PUT("/api/conversations/{uuid}/assignee/team", perm(handleUpdateTeamAssignee, "conversations", "update_team_assignee")) - g.PUT("/api/conversations/{uuid}/priority", perm(handleUpdateConversationPriority, "conversations", "update_priority")) - g.PUT("/api/conversations/{uuid}/status", perm(handleUpdateConversationStatus, "conversations", "update_status")) - - g.GET("/api/conversations/{uuid}", perm(handleGetConversation, "conversations", "read")) - g.GET("/api/conversations/{uuid}/participants", perm(handleGetConversationParticipants, "conversations", "read")) - g.PUT("/api/conversations/{uuid}/last-seen", perm(handleUpdateConversationAssigneeLastSeen, "conversations", "read")) - g.POST("/api/conversations/{uuid}/tags", perm(handleAddConversationTags, "conversations", "update_tags")) - g.GET("/api/conversations/{uuid}/messages", perm(handleGetMessages, "messages", "read")) - g.POST("/api/conversations/{cuuid}/messages", perm(handleSendMessage, "messages", "write")) - g.PUT("/api/conversations/{cuuid}/messages/{uuid}/retry", perm(handleRetryMessage, "messages", "write")) - g.GET("/api/conversations/{cuuid}/messages/{uuid}", perm(handleGetMessage, "messages", "read")) + g.GET("/api/conversations/all", authMiddleware(handleGetAllConversations, "conversations", "read_all")) + g.GET("/api/conversations/unassigned", authMiddleware(handleGetUnassignedConversations, "conversations", "read_unassigned")) + g.GET("/api/conversations/assigned", authMiddleware(handleGetAssignedConversations, "conversations", "read_assigned")) + g.GET("/api/conversations/{uuid}", authMiddleware(handleGetConversation, "conversations", "read")) + g.GET("/api/conversations/{uuid}/participants", authMiddleware(handleGetConversationParticipants, "conversations", "read")) + g.PUT("/api/conversations/{uuid}/assignee/user", authMiddleware(handleUpdateConversationUserAssignee, "conversations", "update_user_assignee")) + g.PUT("/api/conversations/{uuid}/assignee/team", authMiddleware(handleUpdateTeamAssignee, "conversations", "update_team_assignee")) + g.PUT("/api/conversations/{uuid}/priority", authMiddleware(handleUpdateConversationPriority, "conversations", "update_priority")) + g.PUT("/api/conversations/{uuid}/status", authMiddleware(handleUpdateConversationStatus, "conversations", "update_status")) + g.PUT("/api/conversations/{uuid}/last-seen", authMiddleware(handleUpdateConversationAssigneeLastSeen, "conversations", "read")) + g.POST("/api/conversations/{uuid}/tags", authMiddleware(handleAddConversationTags, "conversations", "update_tags")) + g.POST("/api/conversations/{cuuid}/messages", authMiddleware(handleSendMessage, "messages", "write")) + g.GET("/api/conversations/{uuid}/messages", authMiddleware(handleGetMessages, "messages", "read")) + g.PUT("/api/conversations/{cuuid}/messages/{uuid}/retry", authMiddleware(handleRetryMessage, "messages", "write")) + g.GET("/api/conversations/{cuuid}/messages/{uuid}", authMiddleware(handleGetMessage, "messages", "read")) // Status and priority. - g.GET("/api/statuses", sess(authSess(handleGetStatuses))) - g.POST("/api/statuses", perm(handleCreateStatus, "status", "write")) - g.PUT("/api/statuses/{id}", perm(handleUpdateStatus, "status", "write")) - g.DELETE("/api/statuses/{id}", perm(handleDeleteStatus, "status", "delete")) - g.GET("/api/priorities", sess(authSess(handleGetPriorities))) + g.GET("/api/statuses", authMiddleware(handleGetStatuses, "", "")) + g.POST("/api/statuses", authMiddleware(handleCreateStatus, "status", "write")) + g.PUT("/api/statuses/{id}", authMiddleware(handleUpdateStatus, "status", "write")) + g.DELETE("/api/statuses/{id}", authMiddleware(handleDeleteStatus, "status", "delete")) + g.GET("/api/priorities", authMiddleware(handleGetPriorities, "", "")) // Tag. - g.GET("/api/tags", sess(authSess(handleGetTags))) - g.POST("/api/tags", perm(handleCreateTag, "tags", "write")) - g.PUT("/api/tags/{id}", perm(handleUpdateTag, "tags", "write")) - g.DELETE("/api/tags/{id}", perm(handleDeleteTag, "tags", "delete")) + g.GET("/api/tags", authMiddleware(handleGetTags, "", "")) + g.POST("/api/tags", authMiddleware(handleCreateTag, "tags", "write")) + g.PUT("/api/tags/{id}", authMiddleware(handleUpdateTag, "tags", "write")) + g.DELETE("/api/tags/{id}", authMiddleware(handleDeleteTag, "tags", "delete")) // Media. - g.POST("/api/media", sess(handleMediaUpload)) + g.POST("/api/media", authMiddleware(handleMediaUpload, "", "")) // Canned response. - g.GET("/api/canned-responses", sess(authSess(handleGetCannedResponses))) - g.POST("/api/canned-responses", perm(handleCreateCannedResponse, "canned_responses", "write")) - g.PUT("/api/canned-responses/{id}", perm(handleUpdateCannedResponse, "canned_responses", "write")) - g.DELETE("/api/canned-responses/{id}", perm(handleDeleteCannedResponse, "canned_responses", "delete")) + g.GET("/api/canned-responses", authMiddleware(handleGetCannedResponses, "", "")) + g.POST("/api/canned-responses", authMiddleware(handleCreateCannedResponse, "canned_responses", "write")) + g.PUT("/api/canned-responses/{id}", authMiddleware(handleUpdateCannedResponse, "canned_responses", "write")) + g.DELETE("/api/canned-responses/{id}", authMiddleware(handleDeleteCannedResponse, "canned_responses", "delete")) // User. - g.GET("/api/users/me", sess(authSess(handleGetCurrentUser))) - g.PUT("/api/users/me", sess(authSess(handleUpdateCurrentUser))) - g.DELETE("/api/users/me/avatar", sess(authSess(handleDeleteAvatar))) - g.GET("/api/users/compact", sess(authSess(handleGetUsersCompact))) - g.GET("/api/users", perm(handleGetUsers, "users", "read")) - g.GET("/api/users/{id}", perm(handleGetUser, "users", "read")) - g.POST("/api/users", perm(handleCreateUser, "users", "write")) - g.PUT("/api/users/{id}", perm(handleUpdateUser, "users", "write")) + g.GET("/api/users/me", authMiddleware(handleGetCurrentUser, "", "")) + g.PUT("/api/users/me", authMiddleware(handleUpdateCurrentUser, "", "")) + g.DELETE("/api/users/me/avatar", authMiddleware(handleDeleteAvatar, "", "")) + g.GET("/api/users/compact", authMiddleware(handleGetUsersCompact, "", "")) + g.GET("/api/users", authMiddleware(handleGetUsers, "users", "read")) + g.GET("/api/users/{id}", authMiddleware(handleGetUser, "users", "read")) + g.POST("/api/users", authMiddleware(handleCreateUser, "users", "write")) + g.PUT("/api/users/{id}", authMiddleware(handleUpdateUser, "users", "write")) // Team. - g.GET("/api/teams/compact", sess(authSess(handleGetTeamsCompact))) - g.GET("/api/teams", perm(handleGetTeams, "teams", "read")) - g.GET("/api/teams/{id}", perm(handleGetTeam, "teams", "read")) - g.PUT("/api/teams/{id}", perm(handleUpdateTeam, "teams", "write")) - g.POST("/api/teams", perm(handleCreateTeam, "teams", "write")) + g.GET("/api/teams/compact", authMiddleware(handleGetTeamsCompact, "", "")) + g.GET("/api/teams", authMiddleware(handleGetTeams, "teams", "read")) + g.GET("/api/teams/{id}", authMiddleware(handleGetTeam, "teams", "read")) + g.PUT("/api/teams/{id}", authMiddleware(handleUpdateTeam, "teams", "write")) + g.POST("/api/teams", authMiddleware(handleCreateTeam, "teams", "write")) // i18n. g.GET("/api/lang/{lang}", handleGetI18nLang) // Automation. - g.GET("/api/automation/rules", perm(handleGetAutomationRules, "automations", "read")) - g.GET("/api/automation/rules/{id}", perm(handleGetAutomationRule, "automations", "read")) - g.POST("/api/automation/rules", perm(handleCreateAutomationRule, "automations", "write")) - g.PUT("/api/automation/rules/{id}/toggle", perm(handleToggleAutomationRule, "automations", "write")) - g.PUT("/api/automation/rules/{id}", perm(handleUpdateAutomationRule, "automations", "write")) - g.DELETE("/api/automation/rules/{id}", perm(handleDeleteAutomationRule, "automations", "delete")) + g.GET("/api/automation/rules", authMiddleware(handleGetAutomationRules, "automations", "read")) + g.GET("/api/automation/rules/{id}", authMiddleware(handleGetAutomationRule, "automations", "read")) + g.POST("/api/automation/rules", authMiddleware(handleCreateAutomationRule, "automations", "write")) + g.PUT("/api/automation/rules/{id}/toggle", authMiddleware(handleToggleAutomationRule, "automations", "write")) + g.PUT("/api/automation/rules/{id}", authMiddleware(handleUpdateAutomationRule, "automations", "write")) + g.DELETE("/api/automation/rules/{id}", authMiddleware(handleDeleteAutomationRule, "automations", "delete")) // Inbox. - g.GET("/api/inboxes", perm(handleGetInboxes, "inboxes", "read")) - g.GET("/api/inboxes/{id}", perm(handleGetInbox, "inboxes", "read")) - g.POST("/api/inboxes", perm(handleCreateInbox, "inboxes", "write")) - g.PUT("/api/inboxes/{id}/toggle", perm(handleToggleInbox, "inboxes", "write")) - g.PUT("/api/inboxes/{id}", perm(handleUpdateInbox, "inboxes", "write")) - g.DELETE("/api/inboxes/{id}", perm(handleDeleteInbox, "inboxes", "delete")) + g.GET("/api/inboxes", authMiddleware(handleGetInboxes, "inboxes", "read")) + g.GET("/api/inboxes/{id}", authMiddleware(handleGetInbox, "inboxes", "read")) + g.POST("/api/inboxes", authMiddleware(handleCreateInbox, "inboxes", "write")) + g.PUT("/api/inboxes/{id}/toggle", authMiddleware(handleToggleInbox, "inboxes", "write")) + g.PUT("/api/inboxes/{id}", authMiddleware(handleUpdateInbox, "inboxes", "write")) + g.DELETE("/api/inboxes/{id}", authMiddleware(handleDeleteInbox, "inboxes", "delete")) // Role. - g.GET("/api/roles", perm(handleGetRoles, "roles", "read")) - g.GET("/api/roles/{id}", perm(handleGetRole, "roles", "read")) - g.POST("/api/roles", perm(handleCreateRole, "roles", "write")) - g.PUT("/api/roles/{id}", perm(handleUpdateRole, "roles", "write")) - g.DELETE("/api/roles/{id}", perm(handleDeleteRole, "roles", "delete")) + g.GET("/api/roles", authMiddleware(handleGetRoles, "roles", "read")) + g.GET("/api/roles/{id}", authMiddleware(handleGetRole, "roles", "read")) + g.POST("/api/roles", authMiddleware(handleCreateRole, "roles", "write")) + g.PUT("/api/roles/{id}", authMiddleware(handleUpdateRole, "roles", "write")) + g.DELETE("/api/roles/{id}", authMiddleware(handleDeleteRole, "roles", "delete")) // Dashboard. - g.GET("/api/dashboard/global/counts", perm(handleDashboardCounts, "dashboard_global", "read")) - g.GET("/api/dashboard/global/charts", perm(handleDashboardCharts, "dashboard_global", "read")) + g.GET("/api/dashboard/global/counts", authMiddleware(handleDashboardCounts, "dashboard_global", "read")) + g.GET("/api/dashboard/global/charts", authMiddleware(handleDashboardCharts, "dashboard_global", "read")) // Template. - g.GET("/api/templates", perm(handleGetTemplates, "templates", "read")) - g.GET("/api/templates/{id}", perm(handleGetTemplate, "templates", "read")) - g.POST("/api/templates", perm(handleCreateTemplate, "templates", "write")) - g.PUT("/api/templates/{id}", perm(handleUpdateTemplate, "templates", "write")) - g.DELETE("/api/templates/{id}", perm(handleDeleteTemplate, "templates", "delete")) + g.GET("/api/templates", authMiddleware(handleGetTemplates, "templates", "read")) + g.GET("/api/templates/{id}", authMiddleware(handleGetTemplate, "templates", "read")) + g.POST("/api/templates", authMiddleware(handleCreateTemplate, "templates", "write")) + g.PUT("/api/templates/{id}", authMiddleware(handleUpdateTemplate, "templates", "write")) + g.DELETE("/api/templates/{id}", authMiddleware(handleDeleteTemplate, "templates", "delete")) // WebSocket. - g.GET("/api/ws", sess(authSess(func(r *fastglue.Request) error { + g.GET("/api/ws", authMiddleware(func(r *fastglue.Request) error { return handleWS(r, hub) - }))) + }, "", "")) // Frontend pages. - g.GET("/", sess(noAuthPage(serveIndexPage))) - g.GET("/dashboard", sess(authPage(serveIndexPage))) - g.GET("/conversations", sess(authPage(serveIndexPage))) - g.GET("/conversations/{all:*}", sess(authPage(serveIndexPage))) - g.GET("/account/profile", sess(authPage(serveIndexPage))) - g.GET("/admin/{all:*}", sess(authPage(serveIndexPage))) + g.GET("/", notAuthenticatedPage(serveIndexPage)) + g.GET("/dashboard", authenticatedPage(serveIndexPage)) + g.GET("/conversations", authenticatedPage(serveIndexPage)) + g.GET("/conversations/{all:*}", authenticatedPage(serveIndexPage)) + g.GET("/account/profile", authenticatedPage(serveIndexPage)) + g.GET("/admin/{all:*}", authenticatedPage(serveIndexPage)) g.GET("/assets/{all:*}", serveStaticFiles) } diff --git a/cmd/login.go b/cmd/login.go index cbcdc65..a9aec31 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -22,6 +22,7 @@ func handleLogin(r *fastglue.Request) error { app.lo.Error("error saving session", "error", err) return sendErrorEnvelope(r, envelope.NewError(envelope.GeneralError, app.i18n.T("user.errorAcquiringSession"), nil)) } + app.auth.SetCSRFCookie(r) return r.SendEnvelope(user) } diff --git a/cmd/media.go b/cmd/media.go index 752132d..aa05366 100644 --- a/cmd/media.go +++ b/cmd/media.go @@ -133,8 +133,8 @@ func handleMediaUpload(r *fastglue.Request) error { return r.SendEnvelope(media) } -// handleServeUploadedFiles serves uploaded files from the local filesystem or S3. -func handleServeUploadedFiles(r *fastglue.Request) error { +// handleServeMedia serves uploaded media. +func handleServeMedia(r *fastglue.Request) error { var ( app = r.Context.(*App) user = r.RequestCtx.UserValue("user").(umodels.User) @@ -148,28 +148,28 @@ func handleServeUploadedFiles(r *fastglue.Request) error { } // Check if the user has permission to access the linked model. - // TODO: Move this out of here. - if media.Model.String == "messages" { - allowed, err := app.authz.Enforce(user, media.Model.String, "read") - if err != nil { - return r.SendErrorEnvelope(http.StatusInternalServerError, "Error checking permissions", nil, envelope.GeneralError) - } - if !allowed { - return r.SendErrorEnvelope(http.StatusUnauthorized, "Permission denied", nil, envelope.PermissionError) - } + allowed, err := app.authz.EnforceMediaAccess(user, media.Model.String) + if err != nil { + app.lo.Error("error checking media permission", "error", err, "model", media.Model.String, "model_id", media.ModelID) + return sendErrorEnvelope(r, err) + } - // Validate access to the related conversation. + // For messages, check access to the conversation this message is part of. + if media.Model.String == "messages" { conversation, err := app.conversation.GetConversationByMessageID(media.ModelID.Int) if err != nil { return sendErrorEnvelope(r, err) } - - _, err = enforceConversationAccess(app, conversation.UUID, user) + allowed, err = app.authz.EnforceConversationAccess(user, conversation) if err != nil { return sendErrorEnvelope(r, err) } } + if !allowed { + return r.SendErrorEnvelope(http.StatusUnauthorized, "Permission denied", nil, envelope.PermissionError) + } + switch ko.String("upload.provider") { case "fs": fasthttp.ServeFile(r.RequestCtx, filepath.Join(ko.String("upload.fs.upload_path"), uuid)) diff --git a/cmd/middlewares.go b/cmd/middlewares.go index 315a3ca..abfd2f5 100644 --- a/cmd/middlewares.go +++ b/cmd/middlewares.go @@ -9,44 +9,60 @@ import ( "github.com/zerodha/fastglue" ) -func perm(handler fastglue.FastRequestHandler, obj, act string) fastglue.FastRequestHandler { +// authMiddleware does session validation, CSRF checking, and permission enforcement. +func authMiddleware(handler fastglue.FastRequestHandler, object, action string) fastglue.FastRequestHandler { return func(r *fastglue.Request) error { - var app = r.Context.(*App) + app := r.Context.(*App) - user, err := app.auth.ValidateSession(r) + // Validate session and fetch user. + userSession, err := app.auth.ValidateSession(r) if err != nil { + app.lo.Error("error validating session", "error", err) return r.SendErrorEnvelope(http.StatusUnauthorized, "Invalid or expired session", nil, envelope.PermissionError) } - // Fetch user and permissions from DB. - user, err = app.user.Get(user.ID) + user, err := app.user.Get(userSession.ID) if err != nil { return r.SendErrorEnvelope(http.StatusInternalServerError, "Something went wrong", nil, envelope.GeneralError) } + // CSRF check. + cookieToken := string(r.RequestCtx.Request.Header.Cookie("csrf_token")) + hdrToken := string(r.RequestCtx.Request.Header.Peek("X-CSRFTOKEN")) + if cookieToken == "" || hdrToken == "" || cookieToken != hdrToken { + return r.SendErrorEnvelope(http.StatusForbidden, "Invalid CSRF token", nil, envelope.PermissionError) + } + + // Permission enforcement. + if object != "" && action != "" { + ok, err := app.authz.Enforce(user, object, action) + if err != nil { + return r.SendErrorEnvelope(http.StatusInternalServerError, "Error checking permissions", nil, envelope.GeneralError) + } + if !ok { + return r.SendErrorEnvelope(http.StatusForbidden, "Permission denied", nil, envelope.PermissionError) + } + } + // Set user in the request context. r.RequestCtx.SetUserValue("user", user) - // Enforce the permissions with the user, object, and action. - ok, err := app.authz.Enforce(user, obj, act) - if err != nil { - return r.SendErrorEnvelope(http.StatusInternalServerError, "Something went wrong", nil, envelope.GeneralError) - } - if !ok { - return r.SendErrorEnvelope(http.StatusForbidden, "Permission denied", nil, envelope.PermissionError) - } - - // Return handler. + // Proceed to the next handler. return handler(r) } } -// authPage middleware makes sure user is logged in to access the page else redirects to login page. -func authPage(handler fastglue.FastRequestHandler) fastglue.FastRequestHandler { +// getUserFromContext retrieves the authenticated user from the request context. +func getUserFromContext(r *fastglue.Request) (umodels.User, bool) { + user, ok := r.RequestCtx.UserValue("user").(umodels.User) + return user, ok +} + +// authenticatedPage ensures the user is logged in; otherwise, redirects to the login page. +func authenticatedPage(handler fastglue.FastRequestHandler) fastglue.FastRequestHandler { return func(r *fastglue.Request) error { - // Check if user is logged in. If logged in return next handler. - userID, ok := getAuthUserFromSess(r) - if ok && userID > 0 { + user, ok := getUserFromContext(r) + if ok && user.ID > 0 { return handler(r) } nextURI := r.RequestCtx.QueryArgs().Peek("next") @@ -59,54 +75,17 @@ func authPage(handler fastglue.FastRequestHandler) fastglue.FastRequestHandler { } } -// getAuthUserFromSess retrives authUser from request context set by the sess() middleware. -func getAuthUserFromSess(r *fastglue.Request) (int, bool) { - user, ok := r.RequestCtx.UserValue("user").(umodels.User) - if user.ID == 0 || !ok { - return user.ID, false - } - return user.ID, true -} - -func sess(handler fastglue.FastRequestHandler) fastglue.FastRequestHandler { +// notAuthenticatedPage allows access only if the user is not authenticated; otherwise, redirects to the dashboard. +func notAuthenticatedPage(handler fastglue.FastRequestHandler) fastglue.FastRequestHandler { return func(r *fastglue.Request) error { - var app = r.Context.(*App) - user, err := app.auth.ValidateSession(r) - if err != nil { - app.lo.Error("error validating session", "error", err) - return r.SendErrorEnvelope(http.StatusUnauthorized, "Invalid or expired session", nil, envelope.PermissionError) - } - if user.ID >= 0 { - r.RequestCtx.SetUserValue("user", user) + user, _ := getUserFromContext(r) + if user.ID != 0 { + nextURI := string(r.RequestCtx.QueryArgs().Peek("next")) + if nextURI == "" { + nextURI = "/dashboard" + } + return r.RedirectURI(nextURI, fasthttp.StatusFound, nil, "") } return handler(r) } } - -func authSess(handler fastglue.FastRequestHandler) fastglue.FastRequestHandler { - return func(r *fastglue.Request) error { - var ( - userID, ok = getAuthUserFromSess(r) - ) - if !ok || userID <= 0 { - return sendErrorEnvelope(r, - envelope.NewError(envelope.GeneralError, "Invalid or expired session.", nil)) - } - return handler(r) - } -} - -func noAuthPage(handler fastglue.FastRequestHandler) fastglue.FastRequestHandler { - return func(r *fastglue.Request) error { - _, ok := getAuthUserFromSess(r) - if !ok { - return handler(r) - } - // User is logged in direct if `next` is available else redirect. - nextURI := string(r.RequestCtx.QueryArgs().Peek("next")) - if len(nextURI) == 0 { - nextURI = "/dashboard" - } - return r.RedirectURI(nextURI, fasthttp.StatusFound, nil, "") - } -} diff --git a/frontend/src/App.vue b/frontend/src/App.vue index d2c55ce..5c2e40f 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -75,7 +75,6 @@ const allNavLinks = ref([ const bottomLinks = ref([ { to: '/api/logout', - isLink: false, icon: 'lucide:log-out', title: 'Logout' } diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index 2bb488f..8573a13 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -6,8 +6,27 @@ const http = axios.create({ responseType: 'json' }) +// Function to extract CSRF token from cookies +function getCSRFToken() { + const name = 'csrf_token='; + const cookies = document.cookie.split(';'); + for (let i = 0; i < cookies.length; i++) { + let c = cookies[i].trim(); + if (c.indexOf(name) === 0) { + return c.substring(name.length, c.length); + } + } + return ''; +} + // Request interceptor. http.interceptors.request.use((request) => { + // Add csrf token + const token = getCSRFToken() + if (token) { + request.headers['X-CSRFTOKEN'] = token + } + // Set content type for POST/PUT requests if the content type is not set. if ((request.method === 'post' || request.method === 'put') && !request.headers['Content-Type']) { request.headers['Content-Type'] = 'application/x-www-form-urlencoded' diff --git a/frontend/src/components/admin/automation/ActionBox.vue b/frontend/src/components/admin/automation/ActionBox.vue index 460aac5..383515b 100644 --- a/frontend/src/components/admin/automation/ActionBox.vue +++ b/frontend/src/components/admin/automation/ActionBox.vue @@ -6,38 +6,43 @@