Compare commits
3 Commits
6a949ae682
...
330d850790
| Author | SHA1 | Date | |
|---|---|---|---|
| 330d850790 | |||
| 5e6d061330 | |||
| 91cfa901ca |
@ -57,6 +57,9 @@ func (app *application) mount() http.Handler {
|
||||
r.Use(middleware.CleanPath)
|
||||
r.Use(middleware.Recoverer)
|
||||
|
||||
/// CSRF and SESSION TOKEN STUFF:
|
||||
// Store session token in https or whatever only so js can't access it, and the store csrf locally using react or whatever, remove the cookie, and then include it in the http header under 'X-CSRF-Token' or as a hidden input in an html form
|
||||
|
||||
r.Use(middleware.Throttle(100)) // temporary or removable. throttles the whole thing to 1000 concurrent requests
|
||||
// r.Use(cors.Handler(cors.Options{
|
||||
// AllowedOrigins: []string{env.GetString("CORS_ALLOWED_ORIGIN", "http://localhost:5174")},
|
||||
@ -96,7 +99,7 @@ func (app *application) mount() http.Handler {
|
||||
|
||||
r.Route("/user", func(r chi.Router) {
|
||||
r.Route("/{userID}", func(r chi.Router) {
|
||||
r.Use(app.AuthSessionMiddleware, app.CSRFCheckMiddleware, app.CheckUserMatchingMiddleware)
|
||||
r.Use(app.AuthSessionMiddleware, app.CSRFCheckMiddleware, app.CheckUserMatchingMiddleware) // consider the actual use of the user matching middleware and whether an admin should be used instead
|
||||
|
||||
r.Get("/", app.getUserHandler)
|
||||
|
||||
@ -104,16 +107,20 @@ func (app *application) mount() http.Handler {
|
||||
r.Get("/", app.getUsersGroupsHandler)
|
||||
|
||||
r.Route("/{groupID}", func(r chi.Router) {
|
||||
|
||||
r.Get("/", app.getUsersGroupHandler)
|
||||
r.Delete("/", app.removeUserGroupHandler) // maybe this should expect authentication headers to reverify the password when deleting a group you own.
|
||||
|
||||
r.Put("/moderator", app.addGroupModeratorHandler)
|
||||
r.Put("/owner", app.setGroupOwnerHandler)
|
||||
r.Get("/owner", app.getGroupOwnerHandler)
|
||||
|
||||
r.Post("/moderator", app.addGroupModeratorHandler)
|
||||
r.Delete("/moderator/{secondaryuserID}", app.removeModeratorPriviligesHandler)
|
||||
|
||||
r.Get("/users", app.getGroupUsersHandler)
|
||||
r.Delete("/users/{secondaryuserID}", app.removeUserFromGroupHandler)
|
||||
r.Post("/users", app.addGroupUserHandler) // needs to create a new user/do the whole invite thing if they don't already exist
|
||||
r.Delete("/users/{secondaryuserID}", app.removeUserFromGroupHandler) // needs to check if the user is an owner/moderator
|
||||
|
||||
r.Put("/owner", app.setGroupOwnerHandler)
|
||||
})
|
||||
})
|
||||
|
||||
@ -130,6 +137,7 @@ func (app *application) mount() http.Handler {
|
||||
r.Get("/", app.getReceiptImageHandler)
|
||||
r.Put("/", app.changeReceiptImageHandler)
|
||||
r.Delete("/", app.deleteReceiptImageHandler)
|
||||
r.Post("/confirm", app.confirmImageCreationHandler)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -140,7 +148,7 @@ func (app *application) mount() http.Handler {
|
||||
|
||||
})
|
||||
|
||||
r.Use(app.CSRFCheckMiddleware)
|
||||
// r.Use(app.CSRFCheckMiddleware)
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(app.AuthSessionMiddleware)
|
||||
|
||||
@ -89,3 +89,5 @@ func (app *application) registerUserHandler(w http.ResponseWriter, r *http.Reque
|
||||
func (app *application) refreshTokenHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
}
|
||||
|
||||
// func (app *application)
|
||||
|
||||
106
backend/cmd/api/groups.go
Normal file
106
backend/cmd/api/groups.go
Normal file
@ -0,0 +1,106 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"git.ewellenr.ca/receipt_indexer/backend/internal/storage"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
func (app *application) getUserGroups(ctx context.Context, userID int64) (*storage.UserGroups, error) {
|
||||
if !app.config.redisCfg.enabled {
|
||||
return app.store.Groups.GetUserGroups(ctx, userID)
|
||||
}
|
||||
|
||||
usergroups, err := app.cacheStorage.UserGroups.Get(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if usergroups == nil {
|
||||
usergroups, err = app.store.Groups.GetUserGroups(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := app.cacheStorage.UserGroups.Set(ctx, usergroups); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return usergroups, nil
|
||||
}
|
||||
|
||||
func (app *application) getGroup(ctx context.Context, groupID int64) (*storage.Group, error) {
|
||||
if !app.config.redisCfg.enabled {
|
||||
return app.store.Groups.GetByID(ctx, groupID)
|
||||
|
||||
}
|
||||
|
||||
group, err := app.cacheStorage.Groups.Get(ctx, groupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if group == nil {
|
||||
group, err = app.store.Groups.GetByID(ctx, groupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := app.cacheStorage.Groups.Set(ctx, group); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return group, nil
|
||||
}
|
||||
|
||||
func (app *application) getUsersGroupsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
user, err := app.getUserFromURL(w, r)
|
||||
if err != nil { // assume that the writing was already handled
|
||||
return
|
||||
}
|
||||
|
||||
usergroups, err := app.getUserGroups(r.Context(), user.ID)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case storage.ErrNotFound:
|
||||
app.notFoundResponse(w, r, err)
|
||||
return
|
||||
default:
|
||||
app.internalServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := app.jsonResponse(w, http.StatusOK, usergroups); err != nil {
|
||||
app.internalServerError(w, r, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (app *application) getUsersGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
groupID, err := strconv.ParseInt(chi.URLParam(r, "groupID"), 10, 64)
|
||||
if err != nil {
|
||||
app.badRequestResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
group, err := app.getGroup(r.Context(), groupID)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case storage.ErrNotFound:
|
||||
app.notFoundResponse(w, r, err)
|
||||
return
|
||||
default:
|
||||
app.internalServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := app.jsonResponse(w, http.StatusOK, group); err != nil {
|
||||
app.internalServerError(w, r, err)
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,8 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// copied from https://github.com/sikozonpc/GopherSocial/blob/main/cmd/api/json.go
|
||||
|
||||
func writeJSON(w http.ResponseWriter, status int, data any) error {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
@ -18,3 +20,21 @@ func writeJSONError(w http.ResponseWriter, status int, message string) error {
|
||||
|
||||
return writeJSON(w, status, &envelope{Error: message})
|
||||
}
|
||||
|
||||
func readJSON(w http.ResponseWriter, r *http.Request, data any) error {
|
||||
maxBytes := 1_048_578 // 1mb
|
||||
r.Body = http.MaxBytesReader(w, r.Body, int64(maxBytes))
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
decoder.DisallowUnknownFields()
|
||||
|
||||
return decoder.Decode(data)
|
||||
}
|
||||
|
||||
func (app *application) jsonResponse(w http.ResponseWriter, status int, data any) error {
|
||||
type envelope struct {
|
||||
Data any `json:"data"`
|
||||
}
|
||||
|
||||
return writeJSON(w, status, &envelope{Data: data})
|
||||
}
|
||||
|
||||
@ -3,16 +3,47 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
auth_storage "git.ewellenr.ca/receipt_indexer/backend/internal/storage/auth"
|
||||
"git.ewellenr.ca/receipt_indexer/backend/internal/storage"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type userKey string
|
||||
|
||||
const userCtx userKey = "user"
|
||||
|
||||
func (app *application) getUserFromURL(w http.ResponseWriter, r *http.Request) (*storage.User, error) {
|
||||
userID, err := strconv.ParseInt(chi.URLParam(r, "userID"), 10, 64)
|
||||
if err != nil {
|
||||
app.badRequestResponse(w, r, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user, err := app.getUser(r.Context(), userID)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case storage.ErrNotFound:
|
||||
app.notFoundResponse(w, r, err)
|
||||
return nil, err
|
||||
default:
|
||||
app.internalServerError(w, r, err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (app *application) getUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
user, err := app.getUserFromURL(w, r)
|
||||
if err != nil { // assume that the writing was already handled
|
||||
return
|
||||
}
|
||||
|
||||
if err := app.jsonResponse(w, http.StatusOK, user); err != nil {
|
||||
app.internalServerError(w, r, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (app *application) getUsersHandler(w http.ResponseWriter, r *http.Request) {
|
||||
@ -20,7 +51,7 @@ func (app *application) getUsersHandler(w http.ResponseWriter, r *http.Request)
|
||||
func (app *application) deleteUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (app *application) getUser(ctx context.Context, userID int64) (*auth_storage.User, error) {
|
||||
func (app *application) getUser(ctx context.Context, userID int64) (*storage.User, error) {
|
||||
if !app.config.redisCfg.enabled {
|
||||
return app.auth.Users.GetByID(ctx, userID)
|
||||
}
|
||||
@ -44,7 +75,7 @@ func (app *application) getUser(ctx context.Context, userID int64) (*auth_storag
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func getUserFromContext(r *http.Request) *auth_storage.User {
|
||||
user, _ := r.Context().Value(userCtx).(*auth_storage.User)
|
||||
func getUserFromContext(r *http.Request) *storage.User {
|
||||
user, _ := r.Context().Value(userCtx).(*storage.User)
|
||||
return user
|
||||
}
|
||||
|
||||
@ -73,6 +73,7 @@ func (h *Hasher) HashStringWithSalt(in string, salt []byte) (string, error) {
|
||||
return h.Algo.EncodeHash(hash)
|
||||
}
|
||||
|
||||
// Generates a salt and hashes the given string with said salt
|
||||
func (h *Hasher) Hash(in string) ([]byte, error) {
|
||||
salt, err := GenerateRandomBytes(h.SaltLength)
|
||||
if err != nil {
|
||||
|
||||
@ -181,12 +181,17 @@ func (s *SQLUsersStore) Create(ctx context.Context, user *User) error { // creat
|
||||
})
|
||||
}
|
||||
|
||||
func (s *SQLUsersStore) CreateAndInvite(ctx context.Context, user *User, token string, exp time.Duration) error { // figure this out
|
||||
func (s *SQLUsersStore) CreateAndInvite(ctx context.Context, user *User, token string, exp time.Duration) error {
|
||||
// Implement the logic to create a user and invite them using the provided token with an expiration date.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SQLUsersStore) Activate(context.Context, string) error { // what does this do?
|
||||
return nil
|
||||
func (s *SQLUsersStore) Invite(ctx context.Context, exp time.Duration) (int64, string, error) {
|
||||
return -1, "", nil
|
||||
}
|
||||
|
||||
func (s *SQLUsersStore) CreateFromInvite(ctx context.Context, user *User) error {
|
||||
// Implement the logic to create a user from an invite, possibly accepting terms of service or other conditions.
|
||||
}
|
||||
|
||||
func (s *SQLUsersStore) delete(ctx context.Context, id int64, tx *sql.Tx) error {
|
||||
@ -218,14 +223,13 @@ func (s *SQLUsersStore) Delete(ctx context.Context, id int64) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *SQLUsersStore) UpdateUserPass(ctx context.Context, user string, oldPassword string, newPass string) error {
|
||||
return nil
|
||||
}
|
||||
// func (s *SQLUsersStore) UpdateUserPass(ctx context.Context, user string, oldPassword string, newPass string) error {
|
||||
// // Implement the logic to update a user's password if the old password matches and is correct.
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func (s *SQLUsersStore) CheckPass(ctx context.Context, name string, pass string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (s *SQLUsersStore) SigninUser(ctx context.Context, name string, pass string) (bool, *User, error) {
|
||||
// CheckPass(ctx context.Context, name string, pass string) (bool, error)
|
||||
func (s *SQLUsersStore) VerifyUserCredentials(ctx context.Context, username string, pass string) (bool, *User, error) {
|
||||
// Implement the logic to verify user credentials and return a boolean indicating success or failure along with the User details if successful.
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
@ -19,16 +19,24 @@ type Storage struct {
|
||||
GetByID(ctx context.Context, id int64) (*User, error)
|
||||
GetByEmail(context.Context, string) (*User, error)
|
||||
GetByUsername(context.Context, string) (*User, error)
|
||||
Create(context.Context, *User) error // create a non-exported create function which does take in the tx
|
||||
CreateAndInvite(ctx context.Context, user *User, token string, exp time.Duration) error // figure this out
|
||||
Activate(context.Context, string) error // what does this do?
|
||||
Create(context.Context, *User) error // create a non-exported create function which does take in the tx
|
||||
CreateAndInvite(ctx context.Context, user *User, token string, exp time.Duration) error
|
||||
Invite(ctx context.Context, exp time.Duration) (int64, string, error)
|
||||
CreateFromInvite(ctx context.Context, user *User) error
|
||||
Activate(context.Context, string) error
|
||||
Delete(ctx context.Context, id int64) error
|
||||
|
||||
UpdateUserPass(ctx context.Context, user string, oldPassword string, newPass string) error
|
||||
// UpdateUserPass(ctx context.Context, user string, oldPassword string, newPass string) error
|
||||
// Look into resetting user passwords later
|
||||
|
||||
// CheckPass(ctx context.Context, name string, pass string) (bool, error)
|
||||
// SigninUser(ctx context.Context, name string, pass string) (bool, *User, error)
|
||||
VerifyUserCredentials(ctx context.Context, username string, pass string) (bool, *User, error)
|
||||
// ValidCredentials(ctx context.Context, user *User, pass string) (bool, error)
|
||||
|
||||
// Figure out how to do user invites (so user account isn't truly made, no email, username, or password) and also how to do account creation with activation
|
||||
|
||||
// have createandinvite to do the activation stuff. so createandinvite -> activate does that process
|
||||
// use createandinvite to create a blank user have a createFromInvite which takes a *User and adds everything but the id to the database. this should then send an activation link and do the activation. so a two stepper
|
||||
}
|
||||
|
||||
Sessions interface { // store just session tokens, and their corresponding user id
|
||||
@ -75,6 +83,6 @@ type Storage struct {
|
||||
|
||||
func NewSQLRedisMinIOStorage(db *sql.DB) Storage {
|
||||
return Storage{
|
||||
Users: &SQLUsersStore{db},
|
||||
// Users: &SQLUsersStore{db},
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user