From 60da9b7e7bd5d715343cdaeffa148324099fc483 Mon Sep 17 00:00:00 2001 From: Steven Date: Fri, 23 Jun 2023 10:03:47 +0800 Subject: [PATCH] feat: implement redirector api --- api/v1/redirector.go | 37 +++++++++++++++++++++++++ api/v1/v1.go | 6 +++- server/jwt.go | 9 ++---- server/server.go | 6 ++-- web/src/components/ShortcutListView.tsx | 2 +- web/vite.config.ts | 6 +++- 6 files changed, 53 insertions(+), 13 deletions(-) create mode 100644 api/v1/redirector.go diff --git a/api/v1/redirector.go b/api/v1/redirector.go new file mode 100644 index 0000000..b3eb3eb --- /dev/null +++ b/api/v1/redirector.go @@ -0,0 +1,37 @@ +package v1 + +import ( + "net/http" + + "github.com/boojack/shortify/store" + "github.com/labstack/echo/v4" +) + +func (s *APIV1Service) registerRedirectorRoutes(g *echo.Group) { + g.GET("/*", func(c echo.Context) error { + ctx := c.Request().Context() + if len(c.ParamValues()) == 0 { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid shortcut name") + } + shortcutName := c.ParamValues()[0] + shortcut, err := s.Store.GetShortcut(ctx, &store.FindShortcut{ + Name: &shortcutName, + }) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to get shortcut").SetInternal(err) + } + if shortcut == nil { + return echo.NewHTTPError(http.StatusNotFound, "Shortcut not found") + } + if shortcut.Visibility != store.VisibilityPublic { + userID, ok := c.Get(getUserIDContextKey()).(int) + if !ok { + return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized") + } + if shortcut.Visibility == store.VisibilityPrivate && shortcut.CreatorID != userID { + return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized") + } + } + return c.Redirect(http.StatusSeeOther, shortcut.Link) + }) +} diff --git a/api/v1/v1.go b/api/v1/v1.go index 3931059..f1fbd49 100644 --- a/api/v1/v1.go +++ b/api/v1/v1.go @@ -19,9 +19,13 @@ func NewAPIV1Service(profile *profile.Profile, store *store.Store) *APIV1Service } } -func (s *APIV1Service) Start(apiV1Group *echo.Group, secret string) { +func (s *APIV1Service) Start(apiGroup *echo.Group, secret string) { + apiV1Group := apiGroup.Group("/api/v1") s.registerSystemRoutes(apiV1Group) s.registerAuthRoutes(apiV1Group, secret) s.registerUserRoutes(apiV1Group) s.registerShortcutRoutes(apiV1Group) + + redirectorGroup := apiGroup.Group("/o") + s.registerRedirectorRoutes(redirectorGroup) } diff --git a/server/jwt.go b/server/jwt.go index 8f33798..2c87bae 100644 --- a/server/jwt.go +++ b/server/jwt.go @@ -73,22 +73,17 @@ func audienceContains(audience jwt.ClaimStrings, token string) bool { // will try to generate new access token and refresh token. func JWTMiddleware(server *Server, next echo.HandlerFunc, secret string) echo.HandlerFunc { return func(c echo.Context) error { - path := c.Request().URL.Path + path := c.Path() method := c.Request().Method if server.defaultAuthSkipper(c) { return next(c) } - // Skip validation for server status endpoints. - if hasPrefixes(path, "/api/ping", "/api/v1/idp", "/api/user/:id") && method == http.MethodGet { - return next(c) - } - token := findAccessToken(c) if token == "" { // When the request is not authenticated, we allow the user to access the shortcut endpoints for those public shortcuts. - if hasPrefixes(path, "/api/status", "/api/shortcut") && method == http.MethodGet { + if hasPrefixes(path, "/api/v1/status", "/o/*") && method == http.MethodGet { return next(c) } return echo.NewHTTPError(http.StatusUnauthorized, "Missing access token") diff --git a/server/server.go b/server/server.go index d2dce2a..c3c504d 100644 --- a/server/server.go +++ b/server/server.go @@ -60,13 +60,13 @@ func NewServer(profile *profile.Profile, store *store.Store) (*Server, error) { } e.Use(session.Middleware(sessions.NewCookieStore([]byte(secret)))) + apiGroup := e.Group("") // Register API v1 routes. apiV1Service := apiv1.NewAPIV1Service(profile, store) - apiV1Group := e.Group("/api/v1") - apiV1Group.Use(func(next echo.HandlerFunc) echo.HandlerFunc { + apiGroup.Use(func(next echo.HandlerFunc) echo.HandlerFunc { return JWTMiddleware(s, next, string(secret)) }) - apiV1Service.Start(apiV1Group, secret) + apiV1Service.Start(apiGroup, secret) return s, nil } diff --git a/web/src/components/ShortcutListView.tsx b/web/src/components/ShortcutListView.tsx index 125c8bc..19571aa 100644 --- a/web/src/components/ShortcutListView.tsx +++ b/web/src/components/ShortcutListView.tsx @@ -31,7 +31,7 @@ const ShortcutListView: React.FC = (props: Props) => { }; const handleCopyButtonClick = (shortcut: Shortcut) => { - copy(absolutifyLink(`/${shortcut.name}`)); + copy(absolutifyLink(`/o/${shortcut.name}`)); toast.success("Shortcut link copied to clipboard."); }; diff --git a/web/vite.config.ts b/web/vite.config.ts index a020afd..1d9c119 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -1,5 +1,5 @@ -import { defineConfig } from "vite"; import react from "@vitejs/plugin-react-swc"; +import { defineConfig } from "vite"; // https://vitejs.dev/config/ export default defineConfig({ @@ -12,6 +12,10 @@ export default defineConfig({ target: "http://localhost:8082/", changeOrigin: true, }, + "/o": { + target: "http://localhost:8082/", + changeOrigin: true, + }, }, }, });