From 845d339bf4dc36da0ce1be68353e2ef8ada11335 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Sun, 19 Oct 2025 17:27:04 +0200 Subject: [PATCH 01/18] feat(service): update subscription.go --- api/internal/model/pa_session.go | 7 ++++ api/internal/service/subscription.go | 35 ++++++++++++++++++ api/internal/transport/api/req.go | 6 +++ api/internal/transport/api/routes.go | 4 ++ api/internal/transport/api/subscription.go | 43 ++++++++++++++++++++++ 5 files changed, 95 insertions(+) create mode 100644 api/internal/model/pa_session.go diff --git a/api/internal/model/pa_session.go b/api/internal/model/pa_session.go new file mode 100644 index 0000000..43179e7 --- /dev/null +++ b/api/internal/model/pa_session.go @@ -0,0 +1,7 @@ +package model + +type PASession struct { + ID string `json:"id"` + Token string `json:"token"` + PreAuthID string `json:"preauth_id"` +} diff --git a/api/internal/service/subscription.go b/api/internal/service/subscription.go index c1c6891..13d1e11 100644 --- a/api/internal/service/subscription.go +++ b/api/internal/service/subscription.go @@ -2,8 +2,10 @@ package service import ( "context" + "encoding/json" "errors" "log" + "time" "github.com/go-sql-driver/mysql" "github.com/google/uuid" @@ -117,3 +119,36 @@ func (s *Service) DeleteSubscription(ctx context.Context, userID string) error { return nil } + +func (s *Service) AddPASession(ctx context.Context, paSession model.PASession) error { + data, err := json.Marshal(paSession) + if err != nil { + log.Println("failed to marshal pre-auth session to JSON:", err) + return err + } + + err = s.Cache.Set(ctx, "pasession_"+paSession.ID, string(data), 15*time.Minute) + if err != nil { + log.Println("failed to set pre-auth session in Redis:", err) + return err + } + + return nil +} + +func (s *Service) GetPASession(ctx context.Context, id string) (model.PASession, error) { + data, err := s.Cache.Get(ctx, "pasession_"+id) + if err != nil { + log.Println("failed to get pre-auth session from Redis:", err) + return model.PASession{}, err + } + + var paSession model.PASession + err = json.Unmarshal([]byte(data), &paSession) + if err != nil { + log.Println("failed to unmarshal pre-auth session JSON:", err) + return model.PASession{}, err + } + + return paSession, nil +} diff --git a/api/internal/transport/api/req.go b/api/internal/transport/api/req.go index fd5e779..416f293 100644 --- a/api/internal/transport/api/req.go +++ b/api/internal/transport/api/req.go @@ -77,3 +77,9 @@ type ActivateReq struct { type TotpReq struct { OTP string `json:"otp" validate:"required,min=6,max=8"` } + +type PASessionReq struct { + ID string `json:"id" validate:"required,uuid"` + PreAuthID string `json:"preauth_id" validate:"required,uuid"` + Token string `json:"token" validate:"required"` +} diff --git a/api/internal/transport/api/routes.go b/api/internal/transport/api/routes.go index afba0e6..f95c03b 100644 --- a/api/internal/transport/api/routes.go +++ b/api/internal/transport/api/routes.go @@ -33,6 +33,10 @@ func (h *Handler) SetupRoutes(cfg config.APIConfig) { h.Server.Post("/v1/login/begin", limiter.New(), h.BeginLogin) h.Server.Post("/v1/login/finish", limiter.New(), h.FinishLogin) + session := h.Server.Group("/v1/sub/session") + session.Use(auth.NewPSK(cfg)) + session.Post("", h.AddPASession) + v1 := h.Server.Group("/v1") v1.Use(auth.New(cfg, h.Cache, h.Service)) diff --git a/api/internal/transport/api/subscription.go b/api/internal/transport/api/subscription.go index f699beb..7c8f325 100644 --- a/api/internal/transport/api/subscription.go +++ b/api/internal/transport/api/subscription.go @@ -16,6 +16,7 @@ var ( type SubscriptionService interface { GetSubscription(context.Context, string) (model.Subscription, error) UpdateSubscription(context.Context, model.Subscription, string, string, string) error + AddPASession(context.Context, model.PASession) error } // @Summary Get subscription @@ -80,3 +81,45 @@ func (h *Handler) UpdateSubscription(c *fiber.Ctx) error { "message": UpdateSubscriptionSuccess, }) } + +// @Summary Add pre-auth session +// @Description Add pre-auth session +// @Tags subscription +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param body body PASessionReq true "Pre-auth session request" +// @Success 200 {object} SuccessRes +// @Failure 400 {object} ErrorRes +// @Router /sub/session [post] +func (h *Handler) AddPASession(c *fiber.Ctx) error { + req := PASessionReq{} + err := c.BodyParser(&req) + if err != nil { + return c.Status(400).JSON(fiber.Map{ + "error": ErrInvalidRequest, + }) + } + + err = h.Validator.Struct(req) + if err != nil { + return c.Status(400).JSON(fiber.Map{ + "error": ErrInvalidRequest, + }) + } + + paSession := model.PASession{ + ID: req.ID, + PreAuthID: req.PreAuthID, + Token: req.Token, + } + + err = h.Service.AddPASession(c.Context(), paSession) + if err != nil { + return c.Status(400).JSON(fiber.Map{ + "error": err.Error(), + }) + } + + return nil +} From 2dacd84b7ce144540459b6d83a3cec3e3ba31425 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Sun, 19 Oct 2025 17:54:04 +0200 Subject: [PATCH 02/18] feat(api): update subscription.go --- api/internal/middleware/auth/auth.go | 11 ++++++ api/internal/service/subscription.go | 31 +++++++++++++++++ api/internal/transport/api/req.go | 4 +++ api/internal/transport/api/routes.go | 1 + api/internal/transport/api/subscription.go | 39 ++++++++++++++++++++++ 5 files changed, 86 insertions(+) diff --git a/api/internal/middleware/auth/auth.go b/api/internal/middleware/auth/auth.go index 3ab9237..b2e4169 100644 --- a/api/internal/middleware/auth/auth.go +++ b/api/internal/middleware/auth/auth.go @@ -134,6 +134,17 @@ func NewCookieTempAuthn(token string, path string, cfg config.APIConfig) *fiber. } } +func NewCookiePASession(id string) *fiber.Cookie { + return &fiber.Cookie{ + Name: "pasession", + Value: id, + HTTPOnly: true, + Secure: true, + MaxAge: 900, // 15 minutes + Expires: time.Now().Add(15 * time.Minute), + } +} + func NewWebAuthn(cfg config.APIConfig) *webauthn.WebAuthn { var webAuthn *webauthn.WebAuthn config := &webauthn.Config{ diff --git a/api/internal/service/subscription.go b/api/internal/service/subscription.go index 13d1e11..8260c8d 100644 --- a/api/internal/service/subscription.go +++ b/api/internal/service/subscription.go @@ -152,3 +152,34 @@ func (s *Service) GetPASession(ctx context.Context, id string) (model.PASession, return paSession, nil } + +func (s *Service) RotatePASessionId(ctx context.Context, id string) (string, error) { + paSession, err := s.GetPASession(ctx, id) + if err != nil { + log.Println("failed to get pre-auth session for rotation:", err) + return "", err + } + + newID := uuid.New().String() + paSession.ID = newID + + data, err := json.Marshal(paSession) + if err != nil { + log.Println("failed to marshal rotated pre-auth session to JSON:", err) + return "", err + } + + err = s.Cache.Set(ctx, "pasession_"+newID, string(data), 15*time.Minute) + if err != nil { + log.Println("failed to set rotated pre-auth session in Redis:", err) + return "", err + } + + err = s.Cache.Del(ctx, "pasession_"+id) + if err != nil { + log.Println("failed to delete old pre-auth session from Redis:", err) + return "", err + } + + return newID, nil +} diff --git a/api/internal/transport/api/req.go b/api/internal/transport/api/req.go index 416f293..aa1277c 100644 --- a/api/internal/transport/api/req.go +++ b/api/internal/transport/api/req.go @@ -83,3 +83,7 @@ type PASessionReq struct { PreAuthID string `json:"preauth_id" validate:"required,uuid"` Token string `json:"token" validate:"required"` } + +type RotatePASessionReq struct { + ID string `json:"id" validate:"required,uuid"` +} diff --git a/api/internal/transport/api/routes.go b/api/internal/transport/api/routes.go index f95c03b..77a84c3 100644 --- a/api/internal/transport/api/routes.go +++ b/api/internal/transport/api/routes.go @@ -27,6 +27,7 @@ func (h *Handler) SetupRoutes(cfg config.APIConfig) { h.Server.Post("/v1/login", limit.New(5, 10*time.Minute), h.Login) h.Server.Post("/v1/initiatepasswordreset", limiter.New(), h.InitiatePasswordReset) h.Server.Put("/v1/resetpassword", limiter.New(), h.ResetPassword) + h.Server.Put("/v1/rotatepasession", limiter.New(), h.RotatePASession) h.Server.Post("/v1/register/begin", limiter.New(), h.BeginRegistration) h.Server.Post("/v1/register/finish", limiter.New(), h.FinishRegistration) diff --git a/api/internal/transport/api/subscription.go b/api/internal/transport/api/subscription.go index 7c8f325..e30c18e 100644 --- a/api/internal/transport/api/subscription.go +++ b/api/internal/transport/api/subscription.go @@ -11,12 +11,14 @@ import ( var ( UpdateSubscriptionSuccess = "Subscription updated successfully." AddSubscriptionSuccess = "Subscription added successfully." + InvalidPASessionId = "Invalid pre-auth session ID." ) type SubscriptionService interface { GetSubscription(context.Context, string) (model.Subscription, error) UpdateSubscription(context.Context, model.Subscription, string, string, string) error AddPASession(context.Context, model.PASession) error + RotatePASessionId(context.Context, string) (string, error) } // @Summary Get subscription @@ -123,3 +125,40 @@ func (h *Handler) AddPASession(c *fiber.Ctx) error { return nil } + +// @Summary Rotate pre-auth session ID +// @Description Rotate pre-auth session ID +// @Tags subscription +// @Accept json +// @Produce json +// @Param body body RotatePASessionReq true "Rotate pre-auth session request" +// @Success 200 {object} SuccessRes +// @Failure 400 {object} ErrorRes +// @Router /rotatepasession [put] +func (h *Handler) RotatePASession(c *fiber.Ctx) error { + req := RotatePASessionReq{} + err := c.BodyParser(&req) + if err != nil { + return c.Status(400).JSON(fiber.Map{ + "error": InvalidPASessionId, + }) + } + + err = h.Validator.Struct(req) + if err != nil { + return c.Status(400).JSON(fiber.Map{ + "error": InvalidPASessionId, + }) + } + + newID, err := h.Service.RotatePASessionId(c.Context(), req.ID) + if err != nil { + return c.Status(400).JSON(fiber.Map{ + "error": InvalidPASessionId, + }) + } + + c.Cookie(auth.NewCookiePASession(newID)) + + return c.SendStatus(fiber.StatusOK) +} From 34030b9b2dc7a9f69e623f08804eea33bf64f62d Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Mon, 20 Oct 2025 12:53:43 +0200 Subject: [PATCH 03/18] feat(api): update req.go --- api/internal/transport/api/req.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/internal/transport/api/req.go b/api/internal/transport/api/req.go index aa1277c..02a5b65 100644 --- a/api/internal/transport/api/req.go +++ b/api/internal/transport/api/req.go @@ -85,5 +85,5 @@ type PASessionReq struct { } type RotatePASessionReq struct { - ID string `json:"id" validate:"required,uuid"` + ID string `json:"sessionid" validate:"required,uuid"` } From 0f8a940de22e2e825c01306e620a9f3933f1bc1f Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Mon, 20 Oct 2025 12:59:35 +0200 Subject: [PATCH 04/18] feat(app): update AccountSubscription.vue --- app/src/api/subscription.ts | 1 + app/src/components/AccountSubscription.vue | 39 ++++++++++++++-------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/app/src/api/subscription.ts b/app/src/api/subscription.ts index a45f189..f1cc2db 100644 --- a/app/src/api/subscription.ts +++ b/app/src/api/subscription.ts @@ -3,4 +3,5 @@ import { api } from './api' export const subscriptionApi = { get: () => api.get('/sub'), update: (data: any) => api.put('/sub/update', data), + rotateSessionId: (data: any) => api.put('/rotateSession', data), } \ No newline at end of file diff --git a/app/src/components/AccountSubscription.vue b/app/src/components/AccountSubscription.vue index ad82f6d..ba36431 100644 --- a/app/src/components/AccountSubscription.vue +++ b/app/src/components/AccountSubscription.vue @@ -82,8 +82,7 @@ const sub = ref({ const error = ref('') const email = ref(localStorage.getItem('email')) const subid = ref('') -const preauthid = ref('') -const preauthtokenhash = ref('') +const sessionid = ref('') const currentRoute = useRoute() const syncing = ref(false) @@ -99,7 +98,7 @@ const getSubscription = async () => { } const updateSubscription = async () => { - if (!subid.value || !preauthid.value || !preauthtokenhash.value) { + if (!subid.value) { return } @@ -108,8 +107,6 @@ const updateSubscription = async () => { await subscriptionApi.update({ id: sub.value.id, subid: subid.value, - preauthid: preauthid.value, - preauthtokenhash: preauthtokenhash.value, }) await getSubscription() } catch (err) { @@ -121,6 +118,26 @@ const updateSubscription = async () => { } } +const rotateSessionId = async () => { + if (!sessionid.value) { + return + } + + syncing.value = true + try { + await subscriptionApi.rotateSessionId({ + sessionid: sessionid.value, + }) + await updateSubscription() + } catch (err) { + if (axios.isAxiosError(err)) { + error.value = err.message + } + } finally { + syncing.value = false + } +} + const isActive = () => { return sub.value.active_until > new Date().toISOString() } @@ -154,23 +171,17 @@ const parseParams = () => { const q = route.query const first = (v: unknown) => typeof v === 'string' ? v : Array.isArray(v) ? v[0] : '' subid.value = first(q.subid) || (route.params.subid as string) || '' - preauthid.value = first(q.preauthid) || (route.params.preauthid as string) || '' - preauthtokenhash.value = first(q.preauthtokenhash) || (route.params.preauthtokenhash as string) || '' - preauthtokenhash.value = preauthtokenhash.value.replace(/ /g, '+') + sessionid.value = first(q.sessionid) || (route.params.sessionid as string) || '' if (!subid.value || !subid.value.match(/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/)) { return } - if (!preauthid.value || !preauthid.value.match(/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/)) { - return - } - - if (!preauthtokenhash.value) { + if (!sessionid.value || !sessionid.value.match(/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/)) { return } - updateSubscription() + rotateSessionId() } onMounted(() => { From 9671238279b0de30463ed4993bc12a28eaf6a5e5 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Mon, 20 Oct 2025 13:04:33 +0200 Subject: [PATCH 05/18] feat(app): update Signup.vue --- app/src/components/Signup.vue | 46 +++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/app/src/components/Signup.vue b/app/src/components/Signup.vue index 33c7890..0e4b2f3 100644 --- a/app/src/components/Signup.vue +++ b/app/src/components/Signup.vue @@ -104,6 +104,7 @@ import { ref, onMounted, onUpdated } from 'vue' import { useRoute } from 'vue-router' import axios from 'axios' import { userApi } from '../api/user.ts' +import { subscriptionApi } from '../api/subscription.ts' import { startRegistration, browserSupportsWebAuthn } from '@simplewebauthn/browser' import tabs from '@preline/tabs' import Footer from './Footer.vue' @@ -119,8 +120,8 @@ const apiError = ref('') const isLoading = ref(false) const passkeySupported = ref(false) const subid = ref('') -const preauthid = ref('') -const preauthtokenhash = ref('') +const sessionid = ref('') +const syncing = ref(false) const validateEmail = () => { emailError.value = !email.value @@ -129,7 +130,7 @@ const validateEmail = () => { const validateEmailAuthn = () => { emailAuthnError.value = !emailAuthn.value - return !emailAuthnError.value + return !emailAuthnError.value && syncing.value === false } const validatePassword = () => { @@ -140,7 +141,7 @@ const validatePassword = () => { const validate = () => { const validEmail = validateEmail() const validPass = validatePassword() - return validEmail && validPass + return validEmail && validPass && syncing.value === false } const register = async () => { @@ -151,8 +152,6 @@ const register = async () => { email: email.value, password: password.value, subid: subid.value, - preauthid: preauthid.value, - preauthtokenhash: preauthtokenhash.value } try { @@ -181,8 +180,6 @@ const registerWithPasskey = async () => { const data = { email: emailAuthn.value, subid: subid.value, - preauthid: preauthid.value, - preauthtokenhash: preauthtokenhash.value } try { @@ -205,26 +202,43 @@ const registerWithPasskey = async () => { } } +const rotateSessionId = async () => { + if (!sessionid.value) { + return + } + + syncing.value = true + try { + await subscriptionApi.rotateSessionId({ + sessionid: sessionid.value, + }) + } catch (err) { + if (axios.isAxiosError(err)) { + apiError.value = err.message + } + } finally { + syncing.value = false + } +} + const parseParams = () => { const route = useRoute() const q = route.query const first = (v: unknown) => typeof v === 'string' ? v : Array.isArray(v) ? v[0] : '' subid.value = first(q.subid) || (route.params.subid as string) || '' - preauthid.value = first(q.preauthid) || (route.params.preauthid as string) || '' - preauthtokenhash.value = first(q.preauthtokenhash) || (route.params.preauthtokenhash as string) || '' - preauthtokenhash.value = preauthtokenhash.value.replace(/ /g, '+') + sessionid.value = first(q.sessionid) || (route.params.sessionid as string) || '' if (!subid.value || !subid.value.match(/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/)) { console.error('Invalid or missing subid') + return } - if (!preauthid.value || !preauthid.value.match(/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/)) { - console.error('Invalid or missing preauthid') + if (!sessionid.value || !sessionid.value.match(/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/)) { + console.error('Invalid or missing sessionid') + return } - if (!preauthtokenhash.value) { - console.error('Invalid or missing preauthtokenhash') - } + rotateSessionId() } const isLoggedIn = (): boolean => { From 579d87f2df3823e41fd5a3e8fa036fb3dd8a1c66 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Mon, 20 Oct 2025 16:01:30 +0200 Subject: [PATCH 06/18] feat(service): update subscription.go --- api/internal/middleware/auth/auth.go | 3 ++- api/internal/model/pa_session.go | 2 +- api/internal/service/subscription.go | 21 ++++++++++++++++++--- api/internal/transport/api/req.go | 8 +++----- api/internal/transport/api/subscription.go | 8 +++++--- 5 files changed, 29 insertions(+), 13 deletions(-) diff --git a/api/internal/middleware/auth/auth.go b/api/internal/middleware/auth/auth.go index b2e4169..b0448ee 100644 --- a/api/internal/middleware/auth/auth.go +++ b/api/internal/middleware/auth/auth.go @@ -24,6 +24,7 @@ const ( AUTH_COOKIE = "auth" AUTHN_COOKIE = "authn" AUTHN_TEMP_COOKIE = "authntemp" + PA_SESSION_COOKIE = "pasession" USER_ID = "user_id" ) @@ -136,7 +137,7 @@ func NewCookieTempAuthn(token string, path string, cfg config.APIConfig) *fiber. func NewCookiePASession(id string) *fiber.Cookie { return &fiber.Cookie{ - Name: "pasession", + Name: PA_SESSION_COOKIE, Value: id, HTTPOnly: true, Secure: true, diff --git a/api/internal/model/pa_session.go b/api/internal/model/pa_session.go index 43179e7..e71f25d 100644 --- a/api/internal/model/pa_session.go +++ b/api/internal/model/pa_session.go @@ -3,5 +3,5 @@ package model type PASession struct { ID string `json:"id"` Token string `json:"token"` - PreAuthID string `json:"preauth_id"` + PreauthId string `json:"preauth_id"` } diff --git a/api/internal/service/subscription.go b/api/internal/service/subscription.go index 8260c8d..f167643 100644 --- a/api/internal/service/subscription.go +++ b/api/internal/service/subscription.go @@ -2,6 +2,8 @@ package service import ( "context" + "crypto/sha256" + "encoding/base64" "encoding/json" "errors" "log" @@ -18,6 +20,8 @@ var ( ErrPostSubscription = errors.New("Unable to create subscription.") ErrUpdateSubscription = errors.New("Unable to update subscription.") ErrDeleteSubscription = errors.New("Unable to delete subscription.") + ErrPANotFound = errors.New("Pre-auth entry not found.") + ErrPASessionNotFound = errors.New("Pre-auth session not found.") ) type SubscriptionStore interface { @@ -73,14 +77,25 @@ func (s *Service) AddSubscription(ctx context.Context, subscription model.Subscr return nil } -func (s *Service) UpdateSubscription(ctx context.Context, sub model.Subscription, subID string, preauthID string, preauthTokenHash string) error { +func (s *Service) UpdateSubscription(ctx context.Context, sub model.Subscription, subID string, sessionId string) error { + paSession, err := s.GetPASession(ctx, sessionId) + if err != nil { + log.Printf("error creating user: %s", err.Error()) + return ErrPASessionNotFound + } + + preauthID := paSession.PreauthId + token := paSession.Token + tokenHash := sha256.Sum256([]byte(token)) + tokenHashStr := base64.StdEncoding.EncodeToString(tokenHash[:]) + preauth, err := s.Http.GetPreauth(preauthID) if err != nil { log.Printf("error creating user: %s", err.Error()) - return ErrInvalidSubscription + return ErrPANotFound } - if preauth.TokenHash != preauthTokenHash { + if preauth.TokenHash != tokenHashStr { log.Printf("error creating user: Token hash does not match") return ErrTokenHashMismatch } diff --git a/api/internal/transport/api/req.go b/api/internal/transport/api/req.go index 02a5b65..e8297a2 100644 --- a/api/internal/transport/api/req.go +++ b/api/internal/transport/api/req.go @@ -26,10 +26,8 @@ type SignupEmailReq struct { } type SubscriptionReq struct { - ID string `json:"id" validate:"required,uuid"` - SubID string `json:"subid" validate:"required,uuid"` - PreauthID string `json:"preauthid" validate:"required,uuid"` - PreauthTokenHash string `json:"preauthtokenhash" validate:"required"` + ID string `json:"id" validate:"required,uuid"` + SubID string `json:"subid" validate:"required,uuid"` } type AliasReq struct { @@ -80,7 +78,7 @@ type TotpReq struct { type PASessionReq struct { ID string `json:"id" validate:"required,uuid"` - PreAuthID string `json:"preauth_id" validate:"required,uuid"` + PreauthId string `json:"preauth_id" validate:"required,uuid"` Token string `json:"token" validate:"required"` } diff --git a/api/internal/transport/api/subscription.go b/api/internal/transport/api/subscription.go index e30c18e..be32263 100644 --- a/api/internal/transport/api/subscription.go +++ b/api/internal/transport/api/subscription.go @@ -16,7 +16,7 @@ var ( type SubscriptionService interface { GetSubscription(context.Context, string) (model.Subscription, error) - UpdateSubscription(context.Context, model.Subscription, string, string, string) error + UpdateSubscription(context.Context, model.Subscription, string, string) error AddPASession(context.Context, model.PASession) error RotatePASessionId(context.Context, string) (string, error) } @@ -54,6 +54,8 @@ func (h *Handler) GetSubscription(c *fiber.Ctx) error { // @Failure 400 {object} ErrorRes // @Router /subscription/update [put] func (h *Handler) UpdateSubscription(c *fiber.Ctx) error { + sessionId := c.Cookies(auth.PA_SESSION_COOKIE) + req := SubscriptionReq{} err := c.BodyParser(&req) if err != nil { @@ -72,7 +74,7 @@ func (h *Handler) UpdateSubscription(c *fiber.Ctx) error { sub := model.Subscription{} sub.ID = req.ID - err = h.Service.UpdateSubscription(c.Context(), sub, req.SubID, req.PreauthID, req.PreauthTokenHash) + err = h.Service.UpdateSubscription(c.Context(), sub, req.SubID, sessionId) if err != nil { return c.Status(400).JSON(fiber.Map{ "error": err.Error(), @@ -112,7 +114,7 @@ func (h *Handler) AddPASession(c *fiber.Ctx) error { paSession := model.PASession{ ID: req.ID, - PreAuthID: req.PreAuthID, + PreauthId: req.PreauthId, Token: req.Token, } From a2eac230869977b1d8c6ee19767844c1238c58a1 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Mon, 20 Oct 2025 16:09:16 +0200 Subject: [PATCH 07/18] feat(service): update user.go --- api/internal/service/subscription.go | 4 ++-- api/internal/service/user.go | 23 ++++++++++++++++++----- api/internal/transport/api/req.go | 14 +++++--------- api/internal/transport/api/user.go | 7 +++++-- api/internal/transport/api/webauthn.go | 5 ++++- 5 files changed, 34 insertions(+), 19 deletions(-) diff --git a/api/internal/service/subscription.go b/api/internal/service/subscription.go index f167643..165a0eb 100644 --- a/api/internal/service/subscription.go +++ b/api/internal/service/subscription.go @@ -84,12 +84,12 @@ func (s *Service) UpdateSubscription(ctx context.Context, sub model.Subscription return ErrPASessionNotFound } - preauthID := paSession.PreauthId + preauthId := paSession.PreauthId token := paSession.Token tokenHash := sha256.Sum256([]byte(token)) tokenHashStr := base64.StdEncoding.EncodeToString(tokenHash[:]) - preauth, err := s.Http.GetPreauth(preauthID) + preauth, err := s.Http.GetPreauth(preauthId) if err != nil { log.Printf("error creating user: %s", err.Error()) return ErrPANotFound diff --git a/api/internal/service/user.go b/api/internal/service/user.go index 4a969f2..0d361fe 100644 --- a/api/internal/service/user.go +++ b/api/internal/service/user.go @@ -2,7 +2,9 @@ package service import ( "context" + "crypto/sha256" "encoding/base32" + "encoding/base64" "errors" "log" "strings" @@ -91,7 +93,7 @@ func (s *Service) GetUserByEmail(ctx context.Context, email string) (model.User, return user, nil } -func (s *Service) GetUnfinishedSignupOrPostUser(ctx context.Context, user model.User, subID string, preauthID string, preauthTokenHash string) (model.User, error) { +func (s *Service) GetUnfinishedSignupOrPostUser(ctx context.Context, user model.User, subID string, sessionId string) (model.User, error) { email := user.Email pass := user.PasswordPlain user, err := s.Store.GetUserByEmailUnfinishedSignup(ctx, email) @@ -101,7 +103,7 @@ func (s *Service) GetUnfinishedSignupOrPostUser(ctx context.Context, user model. PasswordPlain: pass, IsActive: false, } - err = s.PostUser(ctx, user, subID, preauthID, preauthTokenHash) + err = s.PostUser(ctx, user, subID, sessionId) if err != nil { log.Printf("error creating user: %s", err.Error()) return model.User{}, ErrPostUser @@ -135,14 +137,25 @@ func (s *Service) SaveUser(ctx context.Context, user model.User) error { return nil } -func (s *Service) PostUser(ctx context.Context, user model.User, subID string, preauthID string, preauthTokenHash string) error { - preauth, err := s.Http.GetPreauth(preauthID) +func (s *Service) PostUser(ctx context.Context, user model.User, subID string, sessionId string) error { + paSession, err := s.GetPASession(ctx, sessionId) + if err != nil { + log.Printf("error creating user: %s", err.Error()) + return ErrPASessionNotFound + } + + preauthId := paSession.PreauthId + token := paSession.Token + tokenHash := sha256.Sum256([]byte(token)) + tokenHashStr := base64.StdEncoding.EncodeToString(tokenHash[:]) + + preauth, err := s.Http.GetPreauth(preauthId) if err != nil { log.Printf("error creating user: %s", err.Error()) return ErrInvalidSubscription } - if preauth.TokenHash != preauthTokenHash { + if preauth.TokenHash != tokenHashStr { log.Printf("error creating user: Token hash does not match") return ErrTokenHashMismatch } diff --git a/api/internal/transport/api/req.go b/api/internal/transport/api/req.go index e8297a2..c4931c6 100644 --- a/api/internal/transport/api/req.go +++ b/api/internal/transport/api/req.go @@ -11,18 +11,14 @@ type EmailReq struct { } type SignupUserReq struct { - Email string `json:"email" validate:"required,emailx"` - Password string `json:"password" validate:"password"` - SubID string `json:"subid" validate:"required,uuid"` - PreauthID string `json:"preauthid" validate:"required,uuid"` - PreauthTokenHash string `json:"preauthtokenhash" validate:"required"` + Email string `json:"email" validate:"required,emailx"` + Password string `json:"password" validate:"password"` + SubID string `json:"subid" validate:"required,uuid"` } type SignupEmailReq struct { - Email string `json:"email" validate:"required,emailx"` - SubID string `json:"subid" validate:"required,uuid"` - PreauthID string `json:"preauthid" validate:"required,uuid"` - PreauthTokenHash string `json:"preauthtokenhash" validate:"required"` + Email string `json:"email" validate:"required,emailx"` + SubID string `json:"subid" validate:"required,uuid"` } type SubscriptionReq struct { diff --git a/api/internal/transport/api/user.go b/api/internal/transport/api/user.go index fcb19ab..7f60c4f 100644 --- a/api/internal/transport/api/user.go +++ b/api/internal/transport/api/user.go @@ -37,7 +37,7 @@ type UserService interface { GetUserByCredentials(context.Context, string, string) (model.User, error) GetUserByPassword(context.Context, string, string) (model.User, error) GetUserByEmail(context.Context, string) (model.User, error) - GetUnfinishedSignupOrPostUser(context.Context, model.User, string, string, string) (model.User, error) + GetUnfinishedSignupOrPostUser(context.Context, model.User, string, string) (model.User, error) SaveUser(context.Context, model.User) error DeleteUserRequest(context.Context, string) (string, error) DeleteUser(context.Context, string, string) error @@ -65,6 +65,9 @@ type UserService interface { // @Failure 400 {object} ErrorRes // @Router /register [post] func (h *Handler) Register(c *fiber.Ctx) error { + // Get session ID from cookie + sessionId := c.Cookies(auth.PA_SESSION_COOKIE) + // Parse the request req := SignupUserReq{} err := c.BodyParser(&req) @@ -90,7 +93,7 @@ func (h *Handler) Register(c *fiber.Ctx) error { } // Get unfinished signup user or create new user - user, err = h.Service.GetUnfinishedSignupOrPostUser(c.Context(), user, req.SubID, req.PreauthID, req.PreauthTokenHash) + user, err = h.Service.GetUnfinishedSignupOrPostUser(c.Context(), user, req.SubID, sessionId) if err != nil { return c.Status(400).JSON(fiber.Map{ "error": err.Error(), diff --git a/api/internal/transport/api/webauthn.go b/api/internal/transport/api/webauthn.go index f5d2da2..4b230bb 100644 --- a/api/internal/transport/api/webauthn.go +++ b/api/internal/transport/api/webauthn.go @@ -51,6 +51,9 @@ type CredentialService interface { // @Failure 400 {object} ErrorRes // @Router /register/begin [post] func (h *Handler) BeginRegistration(c *fiber.Ctx) error { + // Get session ID from cookie + sessionId := c.Cookies(auth.PA_SESSION_COOKIE) + // Parse the request req := SignupEmailReq{} err := c.BodyParser(&req) @@ -75,7 +78,7 @@ func (h *Handler) BeginRegistration(c *fiber.Ctx) error { } // Get unfinished signup user or create new user - user, err = h.Service.GetUnfinishedSignupOrPostUser(c.Context(), user, req.SubID, req.PreauthID, req.PreauthTokenHash) + user, err = h.Service.GetUnfinishedSignupOrPostUser(c.Context(), user, req.SubID, sessionId) if err != nil { return c.Status(400).JSON(fiber.Map{ "error": err.Error(), From e0facc5eae61c7be69d0850926a73d364c1fb733 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Mon, 20 Oct 2025 16:11:17 +0200 Subject: [PATCH 08/18] docs: update docs.go --- api/docs/docs.go | 139 ++++++++++++++++++++++++++++++++++-------- api/docs/swagger.json | 139 ++++++++++++++++++++++++++++++++++-------- api/docs/swagger.yaml | 92 ++++++++++++++++++++++------ 3 files changed, 304 insertions(+), 66 deletions(-) diff --git a/api/docs/docs.go b/api/docs/docs.go index cfb9b01..47f083d 100644 --- a/api/docs/docs.go +++ b/api/docs/docs.go @@ -922,6 +922,46 @@ const docTemplate = `{ } } }, + "/rotatepasession": { + "put": { + "description": "Rotate pre-auth session ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "subscription" + ], + "summary": "Rotate pre-auth session ID", + "parameters": [ + { + "description": "Rotate pre-auth session request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.RotatePASessionReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.SuccessRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorRes" + } + } + } + } + }, "/settings": { "get": { "security": [ @@ -1033,6 +1073,51 @@ const docTemplate = `{ } } }, + "/sub/session": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Add pre-auth session", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "subscription" + ], + "summary": "Add pre-auth session", + "parameters": [ + { + "description": "Pre-auth session request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.PASessionReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.SuccessRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorRes" + } + } + } + } + }, "/subscription/update": { "put": { "security": [ @@ -1715,6 +1800,25 @@ const docTemplate = `{ } } }, + "api.PASessionReq": { + "type": "object", + "required": [ + "id", + "preauth_id", + "token" + ], + "properties": { + "id": { + "type": "string" + }, + "preauth_id": { + "type": "string" + }, + "token": { + "type": "string" + } + } + }, "api.RecipientReq": { "type": "object", "required": [ @@ -1749,6 +1853,17 @@ const docTemplate = `{ } } }, + "api.RotatePASessionReq": { + "type": "object", + "required": [ + "sessionid" + ], + "properties": { + "sessionid": { + "type": "string" + } + } + }, "api.SettingsReq": { "type": "object", "required": [ @@ -1776,20 +1891,12 @@ const docTemplate = `{ "type": "object", "required": [ "email", - "preauthid", - "preauthtokenhash", "subid" ], "properties": { "email": { "type": "string" }, - "preauthid": { - "type": "string" - }, - "preauthtokenhash": { - "type": "string" - }, "subid": { "type": "string" } @@ -1799,8 +1906,6 @@ const docTemplate = `{ "type": "object", "required": [ "email", - "preauthid", - "preauthtokenhash", "subid" ], "properties": { @@ -1810,12 +1915,6 @@ const docTemplate = `{ "password": { "type": "string" }, - "preauthid": { - "type": "string" - }, - "preauthtokenhash": { - "type": "string" - }, "subid": { "type": "string" } @@ -1825,20 +1924,12 @@ const docTemplate = `{ "type": "object", "required": [ "id", - "preauthid", - "preauthtokenhash", "subid" ], "properties": { "id": { "type": "string" }, - "preauthid": { - "type": "string" - }, - "preauthtokenhash": { - "type": "string" - }, "subid": { "type": "string" } diff --git a/api/docs/swagger.json b/api/docs/swagger.json index 62e6409..6a54d6c 100644 --- a/api/docs/swagger.json +++ b/api/docs/swagger.json @@ -911,6 +911,46 @@ } } }, + "/rotatepasession": { + "put": { + "description": "Rotate pre-auth session ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "subscription" + ], + "summary": "Rotate pre-auth session ID", + "parameters": [ + { + "description": "Rotate pre-auth session request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.RotatePASessionReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.SuccessRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorRes" + } + } + } + } + }, "/settings": { "get": { "security": [ @@ -1022,6 +1062,51 @@ } } }, + "/sub/session": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Add pre-auth session", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "subscription" + ], + "summary": "Add pre-auth session", + "parameters": [ + { + "description": "Pre-auth session request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.PASessionReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.SuccessRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorRes" + } + } + } + } + }, "/subscription/update": { "put": { "security": [ @@ -1704,6 +1789,25 @@ } } }, + "api.PASessionReq": { + "type": "object", + "required": [ + "id", + "preauth_id", + "token" + ], + "properties": { + "id": { + "type": "string" + }, + "preauth_id": { + "type": "string" + }, + "token": { + "type": "string" + } + } + }, "api.RecipientReq": { "type": "object", "required": [ @@ -1738,6 +1842,17 @@ } } }, + "api.RotatePASessionReq": { + "type": "object", + "required": [ + "sessionid" + ], + "properties": { + "sessionid": { + "type": "string" + } + } + }, "api.SettingsReq": { "type": "object", "required": [ @@ -1765,20 +1880,12 @@ "type": "object", "required": [ "email", - "preauthid", - "preauthtokenhash", "subid" ], "properties": { "email": { "type": "string" }, - "preauthid": { - "type": "string" - }, - "preauthtokenhash": { - "type": "string" - }, "subid": { "type": "string" } @@ -1788,8 +1895,6 @@ "type": "object", "required": [ "email", - "preauthid", - "preauthtokenhash", "subid" ], "properties": { @@ -1799,12 +1904,6 @@ "password": { "type": "string" }, - "preauthid": { - "type": "string" - }, - "preauthtokenhash": { - "type": "string" - }, "subid": { "type": "string" } @@ -1814,20 +1913,12 @@ "type": "object", "required": [ "id", - "preauthid", - "preauthtokenhash", "subid" ], "properties": { "id": { "type": "string" }, - "preauthid": { - "type": "string" - }, - "preauthtokenhash": { - "type": "string" - }, "subid": { "type": "string" } diff --git a/api/docs/swagger.yaml b/api/docs/swagger.yaml index 6c3af6a..e3417e5 100644 --- a/api/docs/swagger.yaml +++ b/api/docs/swagger.yaml @@ -52,6 +52,19 @@ definitions: error: type: string type: object + api.PASessionReq: + properties: + id: + type: string + preauth_id: + type: string + token: + type: string + required: + - id + - preauth_id + - token + type: object api.RecipientReq: properties: id: @@ -74,6 +87,13 @@ definitions: required: - otp type: object + api.RotatePASessionReq: + properties: + sessionid: + type: string + required: + - sessionid + type: object api.SettingsReq: properties: alias_format: @@ -93,16 +113,10 @@ definitions: properties: email: type: string - preauthid: - type: string - preauthtokenhash: - type: string subid: type: string required: - email - - preauthid - - preauthtokenhash - subid type: object api.SignupUserReq: @@ -111,32 +125,20 @@ definitions: type: string password: type: string - preauthid: - type: string - preauthtokenhash: - type: string subid: type: string required: - email - - preauthid - - preauthtokenhash - subid type: object api.SubscriptionReq: properties: id: type: string - preauthid: - type: string - preauthtokenhash: - type: string subid: type: string required: - id - - preauthid - - preauthtokenhash - subid type: object api.SuccessRes: @@ -902,6 +904,32 @@ paths: summary: Reset password tags: - user + /rotatepasession: + put: + consumes: + - application/json + description: Rotate pre-auth session ID + parameters: + - description: Rotate pre-auth session request + in: body + name: body + required: true + schema: + $ref: '#/definitions/api.RotatePASessionReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.SuccessRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.ErrorRes' + summary: Rotate pre-auth session ID + tags: + - subscription /settings: get: consumes: @@ -971,6 +999,34 @@ paths: summary: Get subscription tags: - subscription + /sub/session: + post: + consumes: + - application/json + description: Add pre-auth session + parameters: + - description: Pre-auth session request + in: body + name: body + required: true + schema: + $ref: '#/definitions/api.PASessionReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.SuccessRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.ErrorRes' + security: + - ApiKeyAuth: [] + summary: Add pre-auth session + tags: + - subscription /subscription/update: put: consumes: From 6e40351970a885481410804c54c4af868903bd62 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Mon, 20 Oct 2025 17:48:21 +0200 Subject: [PATCH 09/18] feat(app): update api/subscription.ts --- app/src/api/subscription.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/api/subscription.ts b/app/src/api/subscription.ts index f1cc2db..23c25c4 100644 --- a/app/src/api/subscription.ts +++ b/app/src/api/subscription.ts @@ -3,5 +3,5 @@ import { api } from './api' export const subscriptionApi = { get: () => api.get('/sub'), update: (data: any) => api.put('/sub/update', data), - rotateSessionId: (data: any) => api.put('/rotateSession', data), + rotateSessionId: (data: any) => api.put('/rotatepasession', data), } \ No newline at end of file From fe390ce263c2d5c7e96214735a40e244aa3ea92e Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Mon, 20 Oct 2025 17:50:29 +0200 Subject: [PATCH 10/18] feat(app): update AccountSubscription.vue --- app/src/components/AccountSubscription.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/components/AccountSubscription.vue b/app/src/components/AccountSubscription.vue index ba36431..13f41ea 100644 --- a/app/src/components/AccountSubscription.vue +++ b/app/src/components/AccountSubscription.vue @@ -128,6 +128,7 @@ const rotateSessionId = async () => { await subscriptionApi.rotateSessionId({ sessionid: sessionid.value, }) + await getSubscription() await updateSubscription() } catch (err) { if (axios.isAxiosError(err)) { From d60253419de025e947049461a3fdda8bb10bd325 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Mon, 20 Oct 2025 19:44:14 +0200 Subject: [PATCH 11/18] feat(api): update routes.go --- api/internal/transport/api/routes.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/internal/transport/api/routes.go b/api/internal/transport/api/routes.go index 77a84c3..abeab80 100644 --- a/api/internal/transport/api/routes.go +++ b/api/internal/transport/api/routes.go @@ -34,9 +34,9 @@ func (h *Handler) SetupRoutes(cfg config.APIConfig) { h.Server.Post("/v1/login/begin", limiter.New(), h.BeginLogin) h.Server.Post("/v1/login/finish", limiter.New(), h.FinishLogin) - session := h.Server.Group("/v1/sub/session") + session := h.Server.Group("/v1/pasession") session.Use(auth.NewPSK(cfg)) - session.Post("", h.AddPASession) + session.Post("/add", h.AddPASession) v1 := h.Server.Group("/v1") v1.Use(auth.New(cfg, h.Cache, h.Service)) From e0fdb4769b008c448f0c46408e6191419b7c6304 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Tue, 21 Oct 2025 10:49:13 +0200 Subject: [PATCH 12/18] feat(service): update subscription.go --- api/internal/service/subscription.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/internal/service/subscription.go b/api/internal/service/subscription.go index 165a0eb..05df547 100644 --- a/api/internal/service/subscription.go +++ b/api/internal/service/subscription.go @@ -80,7 +80,7 @@ func (s *Service) AddSubscription(ctx context.Context, subscription model.Subscr func (s *Service) UpdateSubscription(ctx context.Context, sub model.Subscription, subID string, sessionId string) error { paSession, err := s.GetPASession(ctx, sessionId) if err != nil { - log.Printf("error creating user: %s", err.Error()) + log.Printf("error updating subscription: %s", err.Error()) return ErrPASessionNotFound } @@ -91,12 +91,12 @@ func (s *Service) UpdateSubscription(ctx context.Context, sub model.Subscription preauth, err := s.Http.GetPreauth(preauthId) if err != nil { - log.Printf("error creating user: %s", err.Error()) + log.Printf("error updating subscription: %s", err.Error()) return ErrPANotFound } if preauth.TokenHash != tokenHashStr { - log.Printf("error creating user: Token hash does not match") + log.Printf("error updating subscription: Token hash does not match") return ErrTokenHashMismatch } @@ -118,7 +118,7 @@ func (s *Service) UpdateSubscription(ctx context.Context, sub model.Subscription err = s.Http.SignupWebhook(subID) if err != nil { - log.Printf("error creating user: %s", err.Error()) + log.Printf("error updating subscription: %s", err.Error()) return ErrSignupWebhook } From e250f4bb8e7093e6f3b4ae465b27edc91de54c5e Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Tue, 21 Oct 2025 10:59:49 +0200 Subject: [PATCH 13/18] feat(api): update subscription.go --- api/internal/service/subscription.go | 2 +- api/internal/transport/api/subscription.go | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/api/internal/service/subscription.go b/api/internal/service/subscription.go index 05df547..415e586 100644 --- a/api/internal/service/subscription.go +++ b/api/internal/service/subscription.go @@ -105,7 +105,7 @@ func (s *Service) UpdateSubscription(ctx context.Context, sub model.Subscription sub.Tier = preauth.Tier sub.TokenHash = preauth.TokenHash - if sub.ID == "" { + if sub.ID == "" || sub.UserID == "" { log.Printf("error updating subscription: Subscription ID is required") return ErrInvalidSubscription } diff --git a/api/internal/transport/api/subscription.go b/api/internal/transport/api/subscription.go index be32263..f043031 100644 --- a/api/internal/transport/api/subscription.go +++ b/api/internal/transport/api/subscription.go @@ -55,6 +55,7 @@ func (h *Handler) GetSubscription(c *fiber.Ctx) error { // @Router /subscription/update [put] func (h *Handler) UpdateSubscription(c *fiber.Ctx) error { sessionId := c.Cookies(auth.PA_SESSION_COOKIE) + userID := auth.GetUserID(c) req := SubscriptionReq{} err := c.BodyParser(&req) @@ -73,6 +74,12 @@ func (h *Handler) UpdateSubscription(c *fiber.Ctx) error { sub := model.Subscription{} sub.ID = req.ID + sub, err = h.Service.GetSubscription(c.Context(), userID) + if err != nil { + return c.Status(400).JSON(fiber.Map{ + "error": err.Error(), + }) + } err = h.Service.UpdateSubscription(c.Context(), sub, req.SubID, sessionId) if err != nil { From a7fea4dfa1717dd176fa10ac8c42ce4c6efb4e8d Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Tue, 21 Oct 2025 11:01:07 +0200 Subject: [PATCH 14/18] feat(app): update AccountSubscription.vue --- app/src/components/AccountSubscription.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/components/AccountSubscription.vue b/app/src/components/AccountSubscription.vue index 13f41ea..28c9a81 100644 --- a/app/src/components/AccountSubscription.vue +++ b/app/src/components/AccountSubscription.vue @@ -92,7 +92,7 @@ const getSubscription = async () => { sub.value = res.data } catch (err) { if (axios.isAxiosError(err)) { - error.value = err.message + error.value = err.response?.data.error || err.message } } } @@ -111,7 +111,7 @@ const updateSubscription = async () => { await getSubscription() } catch (err) { if (axios.isAxiosError(err)) { - error.value = err.message + error.value = err.response?.data.error || err.message } } finally { syncing.value = false @@ -132,7 +132,7 @@ const rotateSessionId = async () => { await updateSubscription() } catch (err) { if (axios.isAxiosError(err)) { - error.value = err.message + error.value = err.response?.data.error || err.message } } finally { syncing.value = false From c6bda9f4048d475b766d036798233e1148aba8af Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Tue, 21 Oct 2025 11:02:32 +0200 Subject: [PATCH 15/18] feat(api): update main.go --- api/cmd/main.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/api/cmd/main.go b/api/cmd/main.go index b4258c2..3aa83de 100644 --- a/api/cmd/main.go +++ b/api/cmd/main.go @@ -8,7 +8,6 @@ import ( "ivpn.net/email/api/internal/repository" "ivpn.net/email/api/internal/service" "ivpn.net/email/api/internal/transport/api" - "ivpn.net/email/api/internal/utils" ) func Run() error { @@ -17,8 +16,6 @@ func Run() error { return err } - utils.NewLogger(cfg.API) - db, err := repository.NewDB(cfg.DB) if err != nil { return err From db17d332a4020ce9e2a768e95d1d6c715e766f4e Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Tue, 21 Oct 2025 11:18:31 +0200 Subject: [PATCH 16/18] feat(app): update AccountSubscriptionStatus.vue --- app/src/components/AccountSubscriptionStatus.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/components/AccountSubscriptionStatus.vue b/app/src/components/AccountSubscriptionStatus.vue index 31f4093..000b5de 100644 --- a/app/src/components/AccountSubscriptionStatus.vue +++ b/app/src/components/AccountSubscriptionStatus.vue @@ -1,5 +1,5 @@