diff --git a/.gitignore b/.gitignore index c4bf4a2..957043c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,44 @@ blog.db +backend **/.DS_Store +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. + +# Compiled output +web/dist +web/tmp +web/out-tsc +web/bazel-out + +# Node +web/node_modules +web/npm-debug.log +web/yarn-error.log + +# IDEs and editors +web/.idea/ +web/.project +web/.classpath +web/.c9/ +web/*.launch +web/.settings/ +web/*.sublime-workspace + +# Visual Studio Code +web/.vscode/* +web/!.vscode/settings.json +web/!.vscode/tasks.json +web/!.vscode/launch.json +web/!.vscode/extensions.json +web/.history/* + +# Miscellaneous +web/.angular/cache +web/.sass-cache/ +web/connect.lock +web/coverage +web/libpeerconnection.log +testem.log +web/typings + +# System files +web/Thumbs.db diff --git a/backend b/backend deleted file mode 100755 index 163e641..0000000 Binary files a/backend and /dev/null differ diff --git a/internal/auth/controller.go b/internal/auth/controller.go index c8f47e8..920e7ec 100644 --- a/internal/auth/controller.go +++ b/internal/auth/controller.go @@ -50,6 +50,45 @@ func (s *Service) Signup(w http.ResponseWriter, r *http.Request) { } } +// Signup handles user signup by decoding request body, hashing the password, and saving user data to the database. +func (s *Service) ChangePassword(w http.ResponseWriter, r *http.Request) { + var err error + var login Login + var password []byte + + claims, ok := ExtractClaims(r.Context()) + if !ok { + log.Println("Error while extracting claims") + w.WriteHeader(http.StatusInternalServerError) + return + } + + if err = json.NewDecoder(r.Body).Decode(&login); err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + if len([]byte(login.Password)) > 72 { + fmt.Fprint(w, "Password to long, max 72 bytes") + w.WriteHeader(http.StatusBadRequest) + return + } + if password, err = bcrypt.GenerateFromPassword([]byte(login.Password), 6); err != nil { + log.Println("Error: ", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + err = s.db.Model(&model.User{}).Where("id = ?", claims.UserID).Update("password", password).Error + if err != nil { + + log.Printf("Error: %v", err) + w.WriteHeader(http.StatusInternalServerError) + } + + w.WriteHeader(http.StatusOK) +} + // Login handles user login by decoding request body, verifying credentials, and returning a JWT token. func (s *Service) Login(w http.ResponseWriter, r *http.Request) { var login Login diff --git a/internal/initialize/inject.go b/internal/initialize/inject.go index 3cc5424..e5f4a3e 100644 --- a/internal/initialize/inject.go +++ b/internal/initialize/inject.go @@ -19,9 +19,7 @@ func CreateMux(cfg *config.Config) (r *mux.Router) { r.Use(cors.HandlerForOrigin("*")) app(r.PathPrefix("/api").Subrouter(), cfg) - - frontend := web.Frontend - r.PathPrefix("/").Handler(middlewares.AddPrefix("/dist/frontend/browser", http.FileServerFS(frontend))) + frontend(r.PathPrefix("/")) r.Methods("OPTIONS").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // The CORS middleware should set up the headers for you @@ -31,15 +29,28 @@ func CreateMux(cfg *config.Config) (r *mux.Router) { return } +func frontend(r *mux.Route) { + frontend := web.Frontend + r.Handler( + middlewares.AddPrefix("/dist/frontend/browser", + middlewares.FallbackFile( + frontend, + "/dist/frontend/browser/index.html", + http.FileServerFS(frontend), + ), + )) +} + func app(r *mux.Router, cfg *config.Config) { db := model.Init() blg := posts.New(db) auth := auth.New(&cfg.Auth, db) // auth - r.HandleFunc("/login", auth.Login).Methods("POST") - r.HandleFunc("/signup", auth.Signup).Methods("POST") - r.Handle("/logout", auth.Authenticated(auth.Logout)).Methods("DELETE") + r.HandleFunc("/auth/login", auth.Login).Methods("POST") + r.HandleFunc("/auth/signup", auth.Signup).Methods("POST") + r.Handle("/auth/password", auth.Authenticated(auth.ChangePassword)).Methods("PUT") + r.Handle("/auth/logout", auth.Authenticated(auth.Logout)).Methods("DELETE") // Posts r.Handle("/posts", auth.Authenticated(blg.SavePost, model.RoleUser, model.RoleAdmin)).Methods("POST") diff --git a/pkg/middlewares/fallback.go b/pkg/middlewares/fallback.go new file mode 100644 index 0000000..5bb44c0 --- /dev/null +++ b/pkg/middlewares/fallback.go @@ -0,0 +1,61 @@ +package middlewares + +import ( + "bytes" + "io/fs" + "log" + "net/http" +) + +// FallbackFile serves a fallback file if the next handler returns a 404. +// fs: The file system to serve the fallback file from. +// name: The name of the fallback file. +// next: The next handler to serve. +func FallbackFile(fs fs.FS, name string, next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + bw := &buffedResponseWriter{ + w, + 0, + bytes.NewBuffer([]byte{}), + } + next.ServeHTTP(bw, r) + + if bw.statusCode == http.StatusNotFound { + bw.Clear() + + http.ServeFileFS(bw, r, fs, name) + bw.WriteHeader(http.StatusOK) + bw.Header().Add("Content-Type", "text/html; charset=utf-8") + } + bw.Apply() + }) +} + +type buffedResponseWriter struct { + http.ResponseWriter + statusCode int + buff *bytes.Buffer +} + +func (b *buffedResponseWriter) WriteHeader(code int) { + b.statusCode = code +} + +func (b *buffedResponseWriter) Write(p []byte) (int, error) { + return b.buff.Write(p) +} + +func (b *buffedResponseWriter) Clear() { + b.buff.Reset() +} + +func (b *buffedResponseWriter) Apply() { + _, err := b.ResponseWriter.Write(b.buff.Bytes()) + if err != nil { + log.Println("Error while applying buffedResponseWriter: ", err) + b.ResponseWriter.WriteHeader(http.StatusInternalServerError) + } + if b.statusCode != 200 { + b.ResponseWriter.WriteHeader(b.statusCode) + } +} diff --git a/web/src/app/shared/services/auth.service.ts b/web/src/app/shared/services/auth.service.ts index a1f7a82..d151a93 100644 --- a/web/src/app/shared/services/auth.service.ts +++ b/web/src/app/shared/services/auth.service.ts @@ -73,11 +73,11 @@ export class AuthService { login(user: User) { this.http - .post(`${environment.apiRoot}/login`, user) + .post(`${environment.apiRoot}/auth/login`, user) .subscribe((res) => this.jwt.set(res.token)); } logout() { - this.http.delete(`${environment.apiRoot}/logout`).subscribe(() => { + this.http.delete(`${environment.apiRoot}/auth/logout`).subscribe(() => { this.jwt.set(null); }); }