Compare commits

..

No commits in common. "5b633498cbaba268ed97703c07f203034a1fd0b4" and "298aca465ecdd97c47bcc391b53e181d9c9cd00a" have entirely different histories.

4 changed files with 12 additions and 55 deletions

View File

@ -13,7 +13,7 @@ Endpoints protected with `requireAccessToken` middleware:
- `GET /auth/me`: Get own user's data -> `userResponse` DTO (user ID, username, admin status, and timestamps of creation and last update)
- `POST /auth/logout`: Logout the current user -> Cookie that replaces the refresh token with one that expires immediately
- `PUT /auth/owner/`: Update password of the current user with `old_password` and `new_password` -> Cookie with a new refresh token and response with access token and updated user data
- `PUT /auth/owner/`: Update password of the current user with `old_password` and `new_password` -> HTTP 204 response
- `DELETE /auth/owner/`: Delete the current user (as the owner) with `password` -> HTTP 204 response and cookie that replaces the refresh token with one that expires immediately
- `GET /auth/admin/all`: As an administrator, list all users stored in the system (adjustable with pagination URL parameters) -> Array of `userResponse` DTOs
- `DELETE /auth/admin/{userID}`: As an administrator, delete a specific user -> HTTP 204 response

View File

@ -178,11 +178,10 @@ func (q *Queries) ListUsers(ctx context.Context, arg ListUsersParams) ([]User, e
return items, nil
}
const updatePassword = `-- name: UpdatePassword :one
const updatePassword = `-- name: UpdatePassword :exec
UPDATE users
SET password_hash = $2, updated_at = NOW()
WHERE id = $1
RETURNING id, username, password_hash, is_admin, created_at, updated_at
`
type UpdatePasswordParams struct {
@ -190,16 +189,7 @@ type UpdatePasswordParams struct {
PasswordHash string `json:"password_hash"`
}
func (q *Queries) UpdatePassword(ctx context.Context, arg UpdatePasswordParams) (User, error) {
row := q.db.QueryRow(ctx, updatePassword, arg.ID, arg.PasswordHash)
var i User
err := row.Scan(
&i.ID,
&i.Username,
&i.PasswordHash,
&i.IsAdmin,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
func (q *Queries) UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error {
_, err := q.db.Exec(ctx, updatePassword, arg.ID, arg.PasswordHash)
return err
}

View File

@ -71,7 +71,7 @@ type UserStore interface {
ListUsers(ctx context.Context, arg data.ListUsersParams) ([]data.User, error)
GetUserByID(ctx context.Context, id uuid.UUID) (data.User, error)
GetUserByUsername(ctx context.Context, username string) (data.User, error)
UpdatePassword(ctx context.Context, arg data.UpdatePasswordParams) (data.User, error)
UpdatePassword(ctx context.Context, arg data.UpdatePasswordParams) error
DeleteUser(ctx context.Context, id uuid.UUID) error
RevokeAllUserRefreshTokens(ctx context.Context, id uuid.UUID) error
}
@ -271,7 +271,7 @@ func (rs authResource) Get(w http.ResponseWriter, r *http.Request) {
// Handler for updating the current user's password. Performs the same password strength checks as
// the registration handler (`rs.Create`) and revokes any existing refresh tokens the user has
// stored in the database. The new access token and the updated user object's DTO will be returned.
// stored in the database.
func (rs authResource) UpdatePassword(w http.ResponseWriter, r *http.Request) {
type request struct {
OldPassword string `json:"old_password"`
@ -306,51 +306,19 @@ func (rs authResource) UpdatePassword(w http.ResponseWriter, r *http.Request) {
return
}
nUSer, err := rs.Users.UpdatePassword(r.Context(), data.UpdatePasswordParams{
if err := rs.Users.UpdatePassword(r.Context(), data.UpdatePasswordParams{
ID: user.ID,
PasswordHash: string(hashedPassword),
})
if err != nil {
}); err != nil {
respondError(w, http.StatusInternalServerError, "Failed to update password")
return
}
// Revoke all old tokens before generating a new one for this session
if err := rs.Users.RevokeAllUserRefreshTokens(r.Context(), user.ID); err != nil {
log.Error().Msgf("Failed to revoke refresh tokens: %s", err)
}
// Generate a new pair (access & refresh tokens)
tokenPair, err := rs.GenerateTokenPair(r.Context(), user.ID, user.IsAdmin)
if err != nil {
respondError(w, http.StatusInternalServerError, "Failed to generate tokens")
return
}
// Set refresh token into a httpOnly cookie
http.SetCookie(w, &http.Cookie{
Name: "notatest.refresh_token",
Value: tokenPair.RefreshToken,
Path: "/api/auth/cookie",
MaxAge: int(refreshTokenDuration.Seconds()),
HttpOnly: true,
Secure: rs.Config.IsProd,
SameSite: http.SameSiteStrictMode,
})
response := map[string]any{
"access_token": tokenPair.AccessToken,
"user": userResponse{
ID: nUSer.ID,
Username: nUSer.Username,
IsAdmin: nUSer.IsAdmin,
CreatedAt: nUSer.CreatedAt,
UpdatedAt: nUSer.UpdatedAt,
},
}
// Return the new access token and the updated user object
respondJSON(w, http.StatusOK, response)
w.WriteHeader(http.StatusNoContent)
}
// Handler for hard deleting the current user. Requires the user's password as JSON input as a precaution.

View File

@ -24,11 +24,10 @@ WHERE id = $1 LIMIT 1;
SELECT * FROM users
WHERE username = $1 LIMIT 1;
-- name: UpdatePassword :one
-- name: UpdatePassword :exec
UPDATE users
SET password_hash = $2, updated_at = NOW()
WHERE id = $1
RETURNING *;
WHERE id = $1;
-- name: DeleteUser :exec
DELETE FROM users