Compare commits

...

2 Commits

Author SHA1 Message Date
ae
f2e36b09f2
feat: consistent internal error logging 2025-05-04 14:07:23 +03:00
ae
0e9a221728
fix: hide debug print 2025-05-04 13:51:39 +03:00
4 changed files with 30 additions and 5 deletions

View File

@ -175,6 +175,7 @@ func (rs authResource) Create(w http.ResponseWriter, r *http.Request) {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(*req.Password), bcrypt.DefaultCost) hashedPassword, err := bcrypt.GenerateFromPassword([]byte(*req.Password), bcrypt.DefaultCost)
if err != nil { if err != nil {
log.Error().Err(err).Msg("Failed to create user")
respondError(w, http.StatusInternalServerError, "Failed to create user") respondError(w, http.StatusInternalServerError, "Failed to create user")
return return
} }
@ -187,11 +188,14 @@ func (rs authResource) Create(w http.ResponseWriter, r *http.Request) {
if isDuplicateEntry(err) { if isDuplicateEntry(err) {
respondError(w, http.StatusConflict, "Username is already in use") respondError(w, http.StatusConflict, "Username is already in use")
} else { } else {
log.Error().Err(err).Msg("Failed to create user")
respondError(w, http.StatusInternalServerError, "Failed to create user") respondError(w, http.StatusInternalServerError, "Failed to create user")
} }
return return
} }
log.Info().Msgf("User account '%s' created", user.Username)
respondJSON(w, http.StatusCreated, map[string]string{ respondJSON(w, http.StatusCreated, map[string]string{
"id": user.ID.String(), "id": user.ID.String(),
"username": user.Username, "username": user.Username,
@ -235,6 +239,7 @@ func (rs authResource) Login(w http.ResponseWriter, r *http.Request) {
// the database for further token rotations. // the database for further token rotations.
tokenPair, err := rs.GenerateTokenPair(r.Context(), user.ID, user.IsAdmin) tokenPair, err := rs.GenerateTokenPair(r.Context(), user.ID, user.IsAdmin)
if err != nil { if err != nil {
log.Error().Err(err).Msg("Failed to generate tokens")
respondError(w, http.StatusInternalServerError, "Failed to generate tokens") respondError(w, http.StatusInternalServerError, "Failed to generate tokens")
return return
} }
@ -310,6 +315,7 @@ func (rs authResource) UpdatePassword(w http.ResponseWriter, r *http.Request) {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost) hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
if err != nil { if err != nil {
log.Error().Err(err).Msg("Failed to update password")
respondError(w, http.StatusInternalServerError, "Failed to update password") respondError(w, http.StatusInternalServerError, "Failed to update password")
return return
} }
@ -319,6 +325,7 @@ func (rs authResource) UpdatePassword(w http.ResponseWriter, r *http.Request) {
PasswordHash: string(hashedPassword), PasswordHash: string(hashedPassword),
}) })
if err != nil { if err != nil {
log.Error().Err(err).Msg("Failed to update password")
respondError(w, http.StatusInternalServerError, "Failed to update password") respondError(w, http.StatusInternalServerError, "Failed to update password")
return return
} }
@ -331,6 +338,7 @@ func (rs authResource) UpdatePassword(w http.ResponseWriter, r *http.Request) {
// Generate a new pair (access & refresh tokens) // Generate a new pair (access & refresh tokens)
tokenPair, err := rs.GenerateTokenPair(r.Context(), user.ID, user.IsAdmin) tokenPair, err := rs.GenerateTokenPair(r.Context(), user.ID, user.IsAdmin)
if err != nil { if err != nil {
log.Error().Err(err).Msg("Failed to generate tokens")
respondError(w, http.StatusInternalServerError, "Failed to generate tokens") respondError(w, http.StatusInternalServerError, "Failed to generate tokens")
return return
} }
@ -377,6 +385,7 @@ func (rs authResource) OwnerDelete(w http.ResponseWriter, r *http.Request) {
err := rs.Users.DeleteUser(r.Context(), user.ID) err := rs.Users.DeleteUser(r.Context(), user.ID)
if err != nil { if err != nil {
log.Error().Err(err).Msg("Failed to delete user")
respondError(w, http.StatusInternalServerError, "Failed to delete user") respondError(w, http.StatusInternalServerError, "Failed to delete user")
return return
} }
@ -385,9 +394,11 @@ func (rs authResource) OwnerDelete(w http.ResponseWriter, r *http.Request) {
rs.setAuthCookies(w, nil, true) rs.setAuthCookies(w, nil, true)
if err := rs.Users.RevokeAllUserRefreshTokens(r.Context(), user.ID); err != nil { if err := rs.Users.RevokeAllUserRefreshTokens(r.Context(), user.ID); err != nil {
log.Error().Msgf("Failed to revoke refresh tokens: %s", err) log.Warn().Err(err).Msg("Failed to revoke refresh tokens")
} }
log.Info().Msgf("User account '%s' deleted by the owner", user.Username)
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
} }
@ -400,6 +411,7 @@ func (rs authResource) List(w http.ResponseWriter, r *http.Request) {
Offset: offset, Offset: offset,
}) })
if err != nil { if err != nil {
log.Error().Err(err).Msg("Failed to retrieve users")
respondError(w, http.StatusInternalServerError, "Failed to retrieve users") respondError(w, http.StatusInternalServerError, "Failed to retrieve users")
return return
} }
@ -431,6 +443,7 @@ func (rs authResource) AdminDelete(w http.ResponseWriter, r *http.Request) {
} }
if err := rs.Users.DeleteUser(r.Context(), targetID); err != nil { if err := rs.Users.DeleteUser(r.Context(), targetID); err != nil {
log.Error().Err(err).Msg("Failed to delete user")
respondError(w, http.StatusInternalServerError, "Failed to delete user") respondError(w, http.StatusInternalServerError, "Failed to delete user")
return return
} }
@ -439,6 +452,8 @@ func (rs authResource) AdminDelete(w http.ResponseWriter, r *http.Request) {
log.Error().Msgf("Failed to revoke refresh tokens: %s", err) log.Error().Msgf("Failed to revoke refresh tokens: %s", err)
} }
log.Info().Msgf("User account '%s' deleted by an admin", targetID)
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
} }
@ -529,6 +544,7 @@ func (rs authResource) RefreshAccessToken(w http.ResponseWriter, r *http.Request
// Revoke the given (single use) refresh token // Revoke the given (single use) refresh token
if err := rs.RevokeRefreshToken(r.Context(), refreshToken); err != nil { if err := rs.RevokeRefreshToken(r.Context(), refreshToken); err != nil {
log.Error().Err(err).Msg("Failed to revoke token")
respondError(w, http.StatusInternalServerError, "Failed to revoke token") respondError(w, http.StatusInternalServerError, "Failed to revoke token")
return return
} }
@ -542,6 +558,7 @@ func (rs authResource) RefreshAccessToken(w http.ResponseWriter, r *http.Request
// Generate a new pair (access & refresh tokens) // Generate a new pair (access & refresh tokens)
tokenPair, err := rs.GenerateTokenPair(r.Context(), userID, claims.Admin) tokenPair, err := rs.GenerateTokenPair(r.Context(), userID, claims.Admin)
if err != nil { if err != nil {
log.Error().Err(err).Msg("Failed to generate tokens")
respondError(w, http.StatusInternalServerError, "Failed to generate tokens") respondError(w, http.StatusInternalServerError, "Failed to generate tokens")
return return
} }
@ -575,6 +592,7 @@ func (rs authResource) Logout(w http.ResponseWriter, r *http.Request) {
rs.setAuthCookies(w, nil, true) rs.setAuthCookies(w, nil, true)
if err := rs.Tokens.RevokeAllUserRefreshTokens(r.Context(), userID); err != nil { if err := rs.Tokens.RevokeAllUserRefreshTokens(r.Context(), userID); err != nil {
log.Error().Err(err).Msg("Failed to logout")
respondError(w, http.StatusInternalServerError, "Failed to logout") respondError(w, http.StatusInternalServerError, "Failed to logout")
return return
} }

View File

@ -100,6 +100,7 @@ func (rs *notesResource) Create(w http.ResponseWriter, r *http.Request) {
// Metadata object (parent) // Metadata object (parent)
note, err := rs.Notes.CreateNote(r.Context(), userID) note, err := rs.Notes.CreateNote(r.Context(), userID)
if err != nil { if err != nil {
log.Error().Err(err).Msg("Failed to create note")
respondError(w, http.StatusInternalServerError, "Failed to create note") respondError(w, http.StatusInternalServerError, "Failed to create note")
return return
} }
@ -112,6 +113,7 @@ func (rs *notesResource) Create(w http.ResponseWriter, r *http.Request) {
ContentHash: sha1ContentHash(initVersionTitle, initVersionContent), ContentHash: sha1ContentHash(initVersionTitle, initVersionContent),
}) })
if err != nil { if err != nil {
log.Error().Err(err).Msg("Failed to create initial version")
respondError(w, http.StatusInternalServerError, "Failed to create initial version") respondError(w, http.StatusInternalServerError, "Failed to create initial version")
return return
} }
@ -146,6 +148,7 @@ func (rs *notesResource) ListMetadata(w http.ResponseWriter, r *http.Request) {
Offset: offset, Offset: offset,
}) })
if err != nil { if err != nil {
log.Error().Err(err).Msg("Failed to retrieve notes")
respondError(w, http.StatusInternalServerError, "Failed to retrieve notes") respondError(w, http.StatusInternalServerError, "Failed to retrieve notes")
return return
} }
@ -197,6 +200,7 @@ func (rs *notesResource) Delete(w http.ResponseWriter, r *http.Request) {
UserID: userID, // NOTE: using `fullNote.userID` here'd be insecure UserID: userID, // NOTE: using `fullNote.userID` here'd be insecure
}) })
if err != nil { if err != nil {
log.Error().Err(err).Msg("Failed to delete note")
respondError(w, http.StatusInternalServerError, "Failed to delete note") respondError(w, http.StatusInternalServerError, "Failed to delete note")
return return
} }
@ -220,6 +224,7 @@ func (rs *notesResource) GetVersionHistory(w http.ResponseWriter, r *http.Reques
Offset: offset, Offset: offset,
}) })
if err != nil { if err != nil {
log.Error().Err(err).Msg("Failed to get version history")
respondError(w, http.StatusInternalServerError, "Failed to get version history") respondError(w, http.StatusInternalServerError, "Failed to get version history")
return return
} }
@ -303,7 +308,7 @@ func (rs *notesResource) CreateVersion(w http.ResponseWriter, r *http.Request) {
ContentHash: sha1ContentHash(*req.Title, *req.Content), ContentHash: sha1ContentHash(*req.Title, *req.Content),
}) })
if err != nil { if err != nil {
log.Error().Err(err).Msg("Failed to create new note version") log.Error().Err(err).Msg("Failed to create note version")
respondError(w, http.StatusInternalServerError, "Failed to create note version") respondError(w, http.StatusInternalServerError, "Failed to create note version")
return return
} }

View File

@ -67,7 +67,7 @@ func Run(conn *pgx.Conn, q *data.Queries, config SvcConfig) error {
r.Use(middleware.AllowContentType("application/json")) r.Use(middleware.AllowContentType("application/json"))
// Cleanup workers // Cleanup workers
scheduleTokenCleanup(context.Background(), q) scheduleArtifactCleanup(context.Background(), q)
// Routes grouped by functionality (we must prefix the API routes with `/api` // Routes grouped by functionality (we must prefix the API routes with `/api`
// as the domain will be the same for the front and back ends) // as the domain will be the same for the front and back ends)
@ -88,10 +88,12 @@ func Run(conn *pgx.Conn, q *data.Queries, config SvcConfig) error {
// Start worker that automatically cleans up the `notes` (cascading to `note_versions`) and // Start worker that automatically cleans up the `notes` (cascading to `note_versions`) and
// `refresh_tokens` tables from expired (or revoked) entries. The tasks run once during // `refresh_tokens` tables from expired (or revoked) entries. The tasks run once during
// initialization and then once an hour until the backend is shutdown. // initialization and then once an hour until the backend is shutdown.
func scheduleTokenCleanup(ctx context.Context, q *data.Queries) { func scheduleArtifactCleanup(ctx context.Context, q *data.Queries) {
cleanupNotes(ctx, q) cleanupNotes(ctx, q)
cleanupRefreshTokens(ctx, q) cleanupRefreshTokens(ctx, q)
log.Info().Msg("Scheduled database artifact cleanup to run once an hour")
ticker := time.NewTicker(1 * time.Hour) ticker := time.NewTicker(1 * time.Hour)
go func() { go func() {
for range ticker.C { for range ticker.C {

View File

@ -34,7 +34,7 @@ export const parseExpirationPrefix = (title: string): [string, string] => {
const match = title.match(EXPIRATION_DATE_REGEX) const match = title.match(EXPIRATION_DATE_REGEX)
if (match && match[1]) { if (match && match[1]) {
console.log(`[UTIL] Extracted expiration prefix: '${match[1]}'`) // console.log(`[UTIL] Extracted expiration prefix: '${match[1]}'`)
// return [clean title, expiration prefix] // return [clean title, expiration prefix]
return [match[2], match[1]] return [match[2], match[1]]
} }