From 7c210d95818666934ea5e18939db1e25dc01f801 Mon Sep 17 00:00:00 2001 From: Arkaprabha Chakraborty Date: Fri, 13 Feb 2026 00:50:24 +0530 Subject: [PATCH] fix: link authentication --- backend/handlers.go | 103 +++++++++++------------------------ frontend/src/pages/Links.tsx | 72 ++---------------------- 2 files changed, 36 insertions(+), 139 deletions(-) diff --git a/backend/handlers.go b/backend/handlers.go index 7f6d962..9af06fd 100644 --- a/backend/handlers.go +++ b/backend/handlers.go @@ -44,12 +44,10 @@ func uniqueCode() string { // shortenURL creates a new shortened link func shortenURL(c echo.Context) error { type Req struct { - LongURL string `json:"lurl"` - BaseURL string `json:"base_url"` - Code string `json:"code"` - RequiresAuth bool `json:"requires_auth"` - AccessUsername string `json:"access_username"` - AccessPassword string `json:"access_password"` + LongURL string `json:"lurl"` + BaseURL string `json:"base_url"` + Code string `json:"code"` + RequiresAuth bool `json:"requires_auth"` } r := new(Req) @@ -103,26 +101,14 @@ func shortenURL(c echo.Context) error { UpdatedAt: time.Now(), } + // Protected links use the creator's account credentials if r.RequiresAuth { - // If no explicit credentials provided, use the logged-in user's credentials - if r.AccessUsername == "" && r.AccessPassword == "" && userID != nil { - var user User - if err := db.First(&user, *userID).Error; err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to load user") - } - link.AccessUsername = user.Username - link.AccessPassword = user.Password // Already hashed - } else if r.AccessUsername != "" && r.AccessPassword != "" { - // Custom credentials provided - hash, err := bcrypt.GenerateFromPassword([]byte(r.AccessPassword), bcrypt.DefaultCost) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to process password") - } - link.AccessUsername = r.AccessUsername - link.AccessPassword = string(hash) - } else { - return echo.NewHTTPError(http.StatusBadRequest, "Access credentials required for protected links") + var user User + if err := db.First(&user, *userID).Error; err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to load user") } + link.AccessUsername = user.Username + link.AccessPassword = user.Password // Already hashed } if err := db.Create(&link).Error; err != nil { @@ -148,11 +134,10 @@ func fetchLURL(c echo.Context) error { } if link.RequiresAuth { - // Check if user is authenticated and authorized - if username, ok := c.Get("username").(string); ok { - // User is logged in, check if they match the access credentials - if username == link.AccessUsername { - // Auto-authorize logged-in user + // Check if user is authenticated and is the link owner + if userID, ok := c.Get("user_id").(uint); ok { + if link.UserID != nil && *link.UserID == userID { + // Auto-authorize link owner db.Model(&link).UpdateColumn("click_count", gorm.Expr("click_count + 1")) return c.JSON(http.StatusOK, map[string]interface{}{ "lurl": link.LongURL, @@ -162,7 +147,7 @@ func fetchLURL(c echo.Context) error { } } - // User not logged in or doesn't match - require manual auth + // User not logged in or not the owner - require manual auth return c.JSON(http.StatusOK, map[string]interface{}{ "requires_auth": true, "code": link.Code, @@ -204,18 +189,14 @@ func verifyAndRedirect(c echo.Context) error { return echo.NewHTTPError(http.StatusUnauthorized, "Invalid credentials") } - // Check if access_username matches a user account (owner's credentials) + // Verify against the link owner's account password var user User - if db.Where("username = ?", link.AccessUsername).First(&user).Error == nil { - // Verify against user's account password - if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(r.Password)); err != nil { - return echo.NewHTTPError(http.StatusUnauthorized, "Invalid credentials") - } - } else { - // Verify against link's custom password - if err := bcrypt.CompareHashAndPassword([]byte(link.AccessPassword), []byte(r.Password)); err != nil { - return echo.NewHTTPError(http.StatusUnauthorized, "Invalid credentials") - } + if db.Where("username = ?", link.AccessUsername).First(&user).Error != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to verify credentials") + } + + if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(r.Password)); err != nil { + return echo.NewHTTPError(http.StatusUnauthorized, "Invalid credentials") } db.Model(&link).UpdateColumn("click_count", gorm.Expr("click_count + 1")) @@ -246,11 +227,9 @@ func updateLink(c echo.Context) error { } type Req struct { - Code *string `json:"code"` - LongURL *string `json:"long_url"` - RequiresAuth *bool `json:"requires_auth"` - AccessUsername *string `json:"access_username"` - AccessPassword *string `json:"access_password"` + Code *string `json:"code"` + LongURL *string `json:"long_url"` + RequiresAuth *bool `json:"requires_auth"` } r := new(Req) @@ -279,38 +258,20 @@ func updateLink(c echo.Context) error { if r.RequiresAuth != nil { if *r.RequiresAuth { - uname := "" - if r.AccessUsername != nil { - uname = *r.AccessUsername - } - pass := "" - if r.AccessPassword != nil { - pass = *r.AccessPassword - } - if uname == "" || pass == "" { - return echo.NewHTTPError(http.StatusBadRequest, "Access credentials required for protected links") - } - hash, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to process password") + // Enable protection with owner's credentials + var user User + if err := db.First(&user, uid).Error; err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to load user") } link.RequiresAuth = true - link.AccessUsername = uname - link.AccessPassword = string(hash) + link.AccessUsername = user.Username + link.AccessPassword = user.Password // Already hashed } else { + // Disable protection link.RequiresAuth = false link.AccessUsername = "" link.AccessPassword = "" } - } else if link.RequiresAuth { - // Auth already on — allow credential updates without toggling - if r.AccessUsername != nil && *r.AccessUsername != "" { - link.AccessUsername = *r.AccessUsername - } - if r.AccessPassword != nil && *r.AccessPassword != "" { - hash, _ := bcrypt.GenerateFromPassword([]byte(*r.AccessPassword), bcrypt.DefaultCost) - link.AccessPassword = string(hash) - } } link.UpdatedAt = time.Now() diff --git a/frontend/src/pages/Links.tsx b/frontend/src/pages/Links.tsx index 2ce3c84..39d41b4 100644 --- a/frontend/src/pages/Links.tsx +++ b/frontend/src/pages/Links.tsx @@ -215,23 +215,12 @@ interface LinkModalProps { } function LinkModal({ mode, link, onClose, onSaved }: LinkModalProps) { - const { user } = useAuth(); const [longUrl, setLongUrl] = useState(link?.long_url || ''); const [code, setCode] = useState(link?.code || ''); const [requiresAuth, setRequiresAuth] = useState(link?.requires_auth || false); - const [useOwnCredentials, setUseOwnCredentials] = useState(true); - const [accessUsername, setAccessUsername] = useState(link?.access_username || ''); - const [accessPassword, setAccessPassword] = useState(''); const [error, setError] = useState(''); const [saving, setSaving] = useState(false); - // If editing a link with custom credentials (not matching user), default to custom mode - useEffect(() => { - if (link && link.access_username && link.access_username !== user?.username) { - setUseOwnCredentials(false); - } - }, [link, user]); - const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(''); @@ -248,18 +237,12 @@ function LinkModal({ mode, link, onClose, onSaved }: LinkModalProps) { base_url: baseURL, code: code || undefined, requires_auth: requiresAuth, - access_username: requiresAuth && accessUsername ? accessUsername : undefined, - access_password: requiresAuth && accessPassword ? accessPassword : undefined, }); } else if (link) { const body: Record = {}; if (code !== link.code) body.code = code; if (longUrl !== link.long_url) body.long_url = longUrl; if (requiresAuth !== link.requires_auth) body.requires_auth = requiresAuth; - if (requiresAuth) { - if (accessUsername) body.access_username = accessUsername; - if (accessPassword) body.access_password = accessPassword; - } await api.put(`/links/${link.id}`, body); } onSaved(); @@ -334,58 +317,11 @@ function LinkModal({ mode, link, onClose, onSaved }: LinkModalProps) { - {/* Auth credentials */} {requiresAuth && ( -
- - - {useOwnCredentials ? ( -

- Visitors will need to enter your account username and password to access this link. -

- ) : ( - <> -
- - setAccessUsername(e.target.value)} - className="w-full bg-zinc-950 border border-zinc-700 p-3 text-zinc-300 placeholder-zinc-700 focus:outline-none focus:border-zinc-400 focus:ring-1 focus:ring-zinc-400 transition-all font-mono text-sm" - required={requiresAuth && !useOwnCredentials} - /> -
-
- - setAccessPassword(e.target.value)} - className="w-full bg-zinc-950 border border-zinc-700 p-3 text-zinc-300 placeholder-zinc-700 focus:outline-none focus:border-zinc-400 focus:ring-1 focus:ring-zinc-400 transition-all font-mono text-sm" - required={mode === 'create' && requiresAuth && !useOwnCredentials} - /> -
- - )} +
+

+ Protected links can only be accessed with your account credentials. You will be auto-authenticated when logged in. +

)}