all repos

onasty @ a705d9c2248d300dee212abc6a1e453379f84d3a

a one-time notes service
5 files changed, 73 insertions(+), 24 deletions(-)
refactor: don't pass note password in url (#167)

* refactor(api): send note password through post body

* refactor(web): use new api for passworded notes

* refactor(web): fix linter

* refactor(notesrv): use constant value placeholder for empty passwords
Author: Olexandr Smirnov ss2316544@gmail.com
Committed by: GitHub noreply@github.com
Committed at: 2025-07-15 14:36:15 +0300
Parent: 36f59cd
M e2e/apiv1_notes_test.go

@@ -139,6 +139,10 @@ e.Equal(dbNote.Content, "")

e.Equal(dbNote.ReadAt.IsZero(), false) } +type apiv1NoteGetWithPasswordRequest struct { + Password string `json:"password"` +} + func (e *AppTestSuite) TestNoteV1_GetWithPassword() { content := e.uuid() passwd := e.uuid()

@@ -156,9 +160,11 @@ var bodyCreated apiv1NoteCreateResponse

e.readBodyAndUnjsonify(httpResp.Body, &bodyCreated) httpResp = e.httpRequest( - http.MethodGet, - "/api/v1/note/"+bodyCreated.Slug+"?password="+passwd, - nil, + http.MethodPost, + "/api/v1/note/"+bodyCreated.Slug+"/view", + e.jsonify(apiv1NoteGetWithPasswordRequest{ + Password: passwd, + }), ) e.Equal(httpResp.Code, http.StatusOK)

@@ -208,9 +214,11 @@ var bodyCreated apiv1NoteCreateResponse

e.readBodyAndUnjsonify(httpResp.Body, &bodyCreated) httpResp = e.httpRequest( - http.MethodGet, - "/api/v1/note/"+bodyCreated.Slug+"?password="+e.uuid(), - nil, + http.MethodPost, + "/api/v1/note/"+bodyCreated.Slug+"/view", + e.jsonify(apiv1NoteGetWithPasswordRequest{ + Password: e.uuid(), + }), ) e.Equal(httpResp.Code, http.StatusNotFound) }
M internal/service/notesrv/input.go

@@ -2,6 +2,8 @@ package notesrv

import "github.com/olexsmir/onasty/internal/dtos" +const EmptyPassword = "" + // GetNoteBySlugInput used as input for [GetBySlugAndRemoveIfNeeded] type GetNoteBySlugInput struct { // Slug is a note's slug :) *Required*

@@ -13,5 +15,5 @@ Password string

} func (i GetNoteBySlugInput) HasPassword() bool { - return i.Password != "" + return i.Password != EmptyPassword }
M internal/transport/http/apiv1/apiv1.go

@@ -63,6 +63,7 @@

note := r.Group("/note") { note.GET("/:slug", a.getNoteBySlugHandler) + note.POST("/:slug/view", a.getNoteBySlugAndPasswordHandler) note.GET("/:slug/meta", a.getNoteMetadataByIDHandler) possiblyAuthorized := note.Group("", a.couldBeAuthorizedMiddleware)
M internal/transport/http/apiv1/note.go

@@ -60,7 +60,44 @@ note, err := a.notesrv.GetBySlugAndRemoveIfNeeded(

c.Request.Context(), notesrv.GetNoteBySlugInput{ Slug: c.Param("slug"), - Password: c.Query("password"), + Password: notesrv.EmptyPassword, + }, + ) + if err != nil { + errorResponse(c, err) + return + } + + status := http.StatusOK + if !note.ReadAt.IsZero() { + status = http.StatusNotFound + } + + c.JSON(status, getNoteBySlugResponse{ + Content: note.Content, + ReadAt: note.ReadAt, + CreatedAt: note.CreatedAt, + ExpiresAt: note.ExpiresAt, + BurnBeforeExpiration: note.BurnBeforeExpiration, + }) +} + +type getNoteBuySlugAndPasswordRequest struct { + Password string `json:"password"` +} + +func (a *APIV1) getNoteBySlugAndPasswordHandler(c *gin.Context) { + var req getNoteBuySlugAndPasswordRequest + if err := c.ShouldBindJSON(&req); err != nil { + newError(c, http.StatusBadRequest, "invalid request") + return + } + + note, err := a.notesrv.GetBySlugAndRemoveIfNeeded( + c.Request.Context(), + notesrv.GetNoteBySlugInput{ + Slug: c.Param("slug"), + Password: req.Password, }, ) if err != nil {
M web/src/Api/Note.elm

@@ -7,7 +7,6 @@ import Http

import Iso8601 import Json.Encode as E import Time exposing (Posix) -import Url create :

@@ -59,22 +58,24 @@ , password : Maybe String

} -> Effect msg get options = - Effect.sendApiRequest - { endpoint = - "/api/v1/note/" - ++ options.slug - ++ (case options.password of - Just p -> - "?password=" ++ Url.percentEncode p + case options.password of + Just passwd -> + Effect.sendApiRequest + { endpoint = "/api/v1/note/" ++ options.slug ++ "/view" + , method = "POST" + , body = E.object [ ( "password", E.string passwd ) ] |> Http.jsonBody + , onResponse = options.onResponse + , decoder = Note.decode + } - Nothing -> - "" - ) - , method = "GET" - , body = Http.emptyBody - , onResponse = options.onResponse - , decoder = Note.decode - } + Nothing -> + Effect.sendApiRequest + { endpoint = "/api/v1/note/" ++ options.slug + , method = "GET" + , body = Http.emptyBody + , onResponse = options.onResponse + , decoder = Note.decode + } getMetadata :