diff --git a/cmd/client/main.go b/cmd/client/main.go index 3f65b16..fc8c493 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -8,7 +8,12 @@ import ( func main() { http.HandleFunc("/register", handlers.RegisterFormHandler) + http.HandleFunc("/login", handlers.LoginHandler) + http.HandleFunc("/logout", handlers.LogoutHandler) - log.Println("Сервер запущен на :8080") + http.HandleFunc("/dashboard", handlers.AuthMiddleware(handlers.DashboardHandler)) + + // Запуск сервера + log.Println("Клиент запущен на :8080") log.Fatal(http.ListenAndServe(":8080", nil)) } \ No newline at end of file diff --git a/cmd/server/main.go b/cmd/server/main.go index 8c307bf..ef0e69e 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -1,16 +1,15 @@ package main import ( - "log" - - "github.com/pocketbase/pocketbase" + "log" + "github.com/pocketbase/pocketbase" ) func main() { - app := pocketbase.New() + app := pocketbase.New() - if err := app.Start(); err != nil { - log.Fatal(err) - } -} \ No newline at end of file + if err := app.Start(); err != nil { + log.Fatal(err) + } +} diff --git a/go.mod b/go.mod index d50a44e..c031f88 100644 --- a/go.mod +++ b/go.mod @@ -24,14 +24,14 @@ require ( github.com/spf13/cast v1.7.1 // indirect github.com/spf13/cobra v1.9.1 // indirect github.com/spf13/pflag v1.0.6 // indirect - golang.org/x/crypto v0.36.0 // indirect + golang.org/x/crypto v0.37.0 // indirect golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect golang.org/x/image v0.25.0 // indirect golang.org/x/net v0.37.0 // indirect golang.org/x/oauth2 v0.28.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.23.0 // indirect + golang.org/x/sync v0.13.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/text v0.24.0 // indirect modernc.org/libc v1.61.13 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.8.2 // indirect diff --git a/go.sum b/go.sum index 1f5c15e..368df56 100644 --- a/go.sum +++ b/go.sum @@ -64,8 +64,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -78,16 +78,16 @@ golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= diff --git a/handlers/dashboard.go b/handlers/dashboard.go new file mode 100644 index 0000000..f9362f7 --- /dev/null +++ b/handlers/dashboard.go @@ -0,0 +1,149 @@ +package handlers + +import ( + "encoding/json" + "io" + "log" + "net/http" + "pocketbaseSigner/models" +) + + +func DashboardHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "Метод не поддерживается", http.StatusMethodNotAllowed) + return + } + + // Получаем токен из cookie + cookie, err := r.Cookie("pb_auth") + if err != nil { + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + + // Получаем ID пользователя из cookie + userIdCookie, err := r.Cookie("user_id") + if err != nil { + log.Printf("Ошибка при получении ID пользователя: %v", err) + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + + // Получаем данные пользователя + url := "http://localhost:8090/api/collections/users/records/" + userIdCookie.Value + req, err := http.NewRequest("GET", url, nil) + if err != nil { + log.Printf("Ошибка при создании запроса: %v", err) + http.Error(w, "Внутренняя ошибка сервера", http.StatusInternalServerError) + return + } + + req.Header.Set("Authorization", cookie.Value) + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Printf("Ошибка при получении данных пользователя: %v", err) + http.Error(w, "Ошибка при получении данных", http.StatusInternalServerError) + return + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Printf("Ошибка при чтении ответа: %v", err) + http.Error(w, "Ошибка при обработке данных", http.StatusInternalServerError) + return + } + + // Проверяем статус ответа + if resp.StatusCode != http.StatusOK { + log.Printf("Ошибка при получении данных пользователя: %s", string(body)) + http.Error(w, "Ошибка при получении данных пользователя", resp.StatusCode) + return + } + + var userData models.UserData + if err := json.Unmarshal(body, &userData); err != nil { + log.Printf("Ошибка при разборе JSON: %v", err) + http.Error(w, "Ошибка при обработке данных", http.StatusInternalServerError) + return + } + + // Отображаем dashboard + w.Header().Set("Content-Type", "text/html") + dashboardHTML := ` + + + + + + Личный кабинет + + + +
+
+

Личный кабинет

+
+ +
+
+
+

Информация о пользователе

+

Email: ` + userData.Email + `

+

Имя: ` + userData.FirstName + `

+

Фамилия: ` + userData.LastName + `

+

Телефон: ` + userData.Phone + `

+

Дата регистрации: ` + userData.Created + `

+

Последнее обновление: ` + userData.Updated + `

+

Статус верификации: ` + formatVerified(userData.Verified) + `

+
+
+ + + ` + w.Write([]byte(dashboardHTML)) +} + +func formatVerified(verified bool) string { + if verified { + return "Подтвержден" + } + return "Не подтвержден" +} diff --git a/handlers/login.go b/handlers/login.go new file mode 100644 index 0000000..9d89539 --- /dev/null +++ b/handlers/login.go @@ -0,0 +1,124 @@ +package handlers + +import ( + "bytes" + "encoding/json" + "io" + "log" + "net/http" + "pocketbaseSigner/models" +) + +//1. добавить в хэндлере регистрации переход на логин хэндлер +//2. реализовать логин +//3. приступить к миддлвейру + + +func LoginHandler(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodGet { + http.ServeFile(w, r, "./web/login_form.html") + return + } + + if r.Method == http.MethodPost { + err := r.ParseForm() + if err != nil { + http.Error(w, "Ошибка при обработке формы", http.StatusBadRequest) + return + } + + if r.FormValue("email") == "" || r.FormValue("password") == "" { + http.Error(w, "Email и пароль обязательны!", http.StatusBadRequest) + return + } + + // Создаем структуру для запроса + loginData := map[string]string{ + "identity": r.FormValue("email"), + "password": r.FormValue("password"), + } + + // Преобразуем в JSON + jsonData, err := json.Marshal(loginData) + if err != nil { + log.Printf("Ошибка при создании JSON: %v", err) + http.Error(w, "Внутренняя ошибка сервера", http.StatusInternalServerError) + return + } + + url := "http://localhost:8090/api/collections/users/auth-with-password" + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) + if err != nil { + log.Printf("Ошибка при создании запроса: %v", err) + http.Error(w, "Внутренняя ошибка сервера", http.StatusInternalServerError) + return + } + + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Printf("Ошибка при отправке запроса: %v", err) + http.Error(w, "Ошибка при подключении к серверу", http.StatusInternalServerError) + return + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Printf("Ошибка при чтении ответа: %v", err) + http.Error(w, "Ошибка при обработке ответа", http.StatusInternalServerError) + return + } + + // Проверяем статус ответа + if resp.StatusCode != http.StatusOK { + log.Printf("Ошибка авторизации: %s", string(body)) + http.Error(w, "Неверный email или пароль", http.StatusUnauthorized) + return + } + + // Парсим ответ для получения токена + var authResp models.AuthResponse + if err := json.Unmarshal(body, &authResp); err != nil { + log.Printf("Ошибка при разборе JSON: %v", err) + http.Error(w, "Ошибка при обработке ответа", http.StatusInternalServerError) + return + } + + // Проверяем наличие токена в ответе + if authResp.Token == "" { + log.Printf("Токен отсутствует в ответе: %s", string(body)) + http.Error(w, "Ошибка авторизации: токен не получен", http.StatusInternalServerError) + return + } + + // Логируем успешную авторизацию + log.Printf("Успешная авторизация пользователя: %s", authResp.Token) + + // Сохраняем токен в cookie + http.SetCookie(w, &http.Cookie{ + Name: "pb_auth", + Value: authResp.Token, + Path: "/", + HttpOnly: true, + Secure: true, + SameSite: http.SameSiteStrictMode, + }) + + // Сохраняем ID пользователя в отдельную cookie + http.SetCookie(w, &http.Cookie{ + Name: "user_id", + Value: authResp.Record.ID, + Path: "/", + HttpOnly: true, + Secure: true, + SameSite: http.SameSiteStrictMode, + }) + + // Перенаправляем на dashboard + http.Redirect(w, r, "/dashboard", http.StatusSeeOther) + } +} diff --git a/handlers/logout.go b/handlers/logout.go new file mode 100644 index 0000000..6fb8a93 --- /dev/null +++ b/handlers/logout.go @@ -0,0 +1,31 @@ +package handlers + +import ( + "net/http" +) + +func LogoutHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Метод не поддерживается", http.StatusMethodNotAllowed) + return + } + + // Удаляем cookie с токеном + http.SetCookie(w, &http.Cookie{ + Name: "pb_auth", + Value: "", + Path: "/", + MaxAge: -1, + }) + + // Удаляем cookie с ID пользователя + http.SetCookie(w, &http.Cookie{ + Name: "user_id", + Value: "", + Path: "/", + MaxAge: -1, + }) + + // Перенаправляем на страницу логина + http.Redirect(w, r, "/login", http.StatusSeeOther) +} diff --git a/handlers/middleware.go b/handlers/middleware.go new file mode 100644 index 0000000..861b6b1 --- /dev/null +++ b/handlers/middleware.go @@ -0,0 +1,45 @@ +package handlers + +import ( + "log" + "net/http" +) + +func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Получаем токен из cookie + cookie, err := r.Cookie("pb_auth") + if err != nil { + // Если токена нет, перенаправляем на страницу логина + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + + // Проверяем валидность токена через PocketBase + req, err := http.NewRequest("POST", "http://localhost:8090/api/collections/users/auth-refresh", nil) + if err != nil { + log.Printf("Ошибка при создании запроса: %v", err) + http.Error(w, "Внутренняя ошибка сервера", http.StatusInternalServerError) + return + } + + req.Header.Set("Authorization", cookie.Value) + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Printf("Ошибка при проверке токена: %v", err) + http.Error(w, "Ошибка при проверке авторизации", http.StatusInternalServerError) + return + } + defer resp.Body.Close() + + // Если токен невалиден, перенаправляем на страницу логина + if resp.StatusCode != http.StatusOK { + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + + // Если токен валиден, продолжаем выполнение + next.ServeHTTP(w, r) + } +} diff --git a/handlers/register.go b/handlers/register.go index 1de3ab3..32d6c7b 100644 --- a/handlers/register.go +++ b/handlers/register.go @@ -1,91 +1,93 @@ package handlers import ( - "fmt" - "log" - "net/http" - "pocketbaseSigner/models" - "bytes" - "encoding/json" + "bytes" + "encoding/json" + "log" + "net/http" + "pocketbaseSigner/models" ) - func RegisterFormHandler(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodGet { - http.ServeFile(w, r, "./web/register_form.html") - return - } + if r.Method == http.MethodGet { + http.ServeFile(w, r, "./web/register_form.html") + return + } - if r.Method == http.MethodPost { - err := r.ParseForm() - if err != nil { - http.Error(w, "Ошибка при обработке формы", http.StatusBadRequest) - return - } + if r.Method == http.MethodPost { + err := r.ParseForm() + if err != nil { + http.Error(w, "Ошибка при обработке формы", http.StatusBadRequest) + return + } - user := models.UserForm{ - Email: r.FormValue("email"), - Password: r.FormValue("password"), - PasswordConfirm: r.FormValue("password_confirm"), - FirstName: r.FormValue("firstname"), - LastName: r.FormValue("lastname"), - Phone: r.FormValue("phone"), - } + user := models.RegisterForm{ + Email: r.FormValue("email"), + Password: r.FormValue("password"), + PasswordConfirm: r.FormValue("password_confirm"), + FirstName: r.FormValue("firstname"), + LastName: r.FormValue("lastname"), + Phone: r.FormValue("phone"), + } - // нужно для маршалинга потому что переменные с маленькой буквы потеряются - // если все переменные делать с большой буквы, то pocketbase не примет - dataMap := map[string]string{ - "email": user.Email, - "password": user.Password, - "passwordConfirm": user.PasswordConfirm, - "FirstName": user.FirstName, - "LastName": user.LastName, - "Phone": user.Phone, - } - - data, err := json.Marshal(dataMap) + // Проверяем совпадение паролей + if r.FormValue("password") != r.FormValue("password_confirm") { + http.Error(w, "Пароли не совпали", http.StatusBadRequest) + return + } + // Проверяем обязательные поля + if user.Email == "" || user.Password == "" { + http.Error(w, "Email и пароль обязательны!", http.StatusBadRequest) + return + } - if r.FormValue("password") != r.FormValue("password_confirm") { - http.Error(w, "Пароли не совпали", http.StatusBadRequest) - return - } - // Проверка - if user.Email == "" || user.Password == "" { - http.Error(w, "Email и пароль обязательны!", http.StatusBadRequest) - return - } + // Подготавливаем данные для отправки + dataMap := map[string]string{ + "email": user.Email, + "password": user.Password, + "passwordConfirm": user.PasswordConfirm, + "FirstName": user.FirstName, + "LastName": user.LastName, + "Phone": user.Phone, + } - log.Printf("Получен пользователь: %+v\n", user) - fmt.Println(json.Marshal(user)) - fmt.Println() - if err != nil { - fmt.Println(err) - return - } + data, err := json.Marshal(dataMap) + if err != nil { + log.Printf("Ошибка при создании JSON: %v", err) + http.Error(w, "Внутренняя ошибка сервера", http.StatusInternalServerError) + return + } - url:= "http://localhost:8090/api/collections/users/records" - fmt.Println(bytes.NewBuffer(data)) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(data)) - if err != nil { - fmt.Println(err) - return - } + log.Printf("Отправляем данные пользователя: %+v\n", user) - fmt.Println(bytes.NewBuffer(data)) - fmt.Println() + url := "http://localhost:8090/api/collections/users/records" + req, err := http.NewRequest("POST", url, bytes.NewBuffer(data)) + if err != nil { + log.Printf("Ошибка при создании запроса: %v", err) + http.Error(w, "Внутренняя ошибка сервера", http.StatusInternalServerError) + return + } - req.Header.Set("Content-Type", "application/json") + req.Header.Set("Content-Type", "application/json") - fmt.Println(req) - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - fmt.Println(err) - return - } - defer resp.Body.Close() + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Printf("Ошибка при отправке запроса: %v", err) + http.Error(w, "Ошибка при подключении к серверу", http.StatusInternalServerError) + return + } + defer resp.Body.Close() - http.Error(w, "Данные отправлены", http.StatusOK) + // Проверяем статус ответа + if resp.StatusCode != http.StatusOK { + log.Printf("Ошибка при регистрации: %v", resp.StatusCode) + http.Error(w, "Ошибка при регистрации", resp.StatusCode) + return + } + + // Перенаправляем на страницу логина после успешной регистрации + http.Redirect(w, r, "/login", http.StatusSeeOther) + } } -} \ No newline at end of file diff --git a/models/auth.go b/models/auth.go new file mode 100644 index 0000000..2e87355 --- /dev/null +++ b/models/auth.go @@ -0,0 +1,12 @@ +package models + +type AuthResponse struct { + Record struct { + ID string `json:"id"` + Email string `json:"email"` + FirstName string `json:"FirstName"` + LastName string `json:"LastName"` + Phone string `json:"Phone"` + } `json:"record"` + Token string `json:"token"` +} \ No newline at end of file diff --git a/models/forms.go b/models/forms.go new file mode 100644 index 0000000..fdb4daa --- /dev/null +++ b/models/forms.go @@ -0,0 +1,15 @@ +package models + +type RegisterForm struct { + Email string + Password string + PasswordConfirm string + FirstName string + LastName string + Phone string +} + +type LoginForm struct{ + Email string + Password string +} \ No newline at end of file diff --git a/models/user.go b/models/user.go index c5216c1..f5e8d9d 100644 --- a/models/user.go +++ b/models/user.go @@ -1,17 +1,24 @@ package models -type UserForm struct { - Email string - Password string - PasswordConfirm string - FirstName string - LastName string - Phone string -} type UserResponse struct { ID string `json:"id"` Email string `json:"email"` FirstName string `json:"first_name"` LastName string `json:"last_name"` +} + +type UserData struct { + CollectionId string `json:"collectionId"` + CollectionName string `json:"collectionName"` + ID string `json:"id"` + Email string `json:"email"` + FirstName string `json:"FirstName"` + LastName string `json:"LastName"` + Phone string `json:"Phone"` + EmailVisibility bool `json:"emailVisibility"` + Verified bool `json:"verified"` + Avatar string `json:"avatar"` + Created string `json:"created"` + Updated string `json:"updated"` } \ No newline at end of file diff --git a/pb_data/auxiliary.db b/pb_data/auxiliary.db index 7ddd33f..e86746f 100644 Binary files a/pb_data/auxiliary.db and b/pb_data/auxiliary.db differ diff --git a/pb_data/data.db b/pb_data/data.db index 6828647..f5beb1f 100644 Binary files a/pb_data/data.db and b/pb_data/data.db differ diff --git a/web/login_form.html b/web/login_form.html new file mode 100644 index 0000000..9bbcec9 --- /dev/null +++ b/web/login_form.html @@ -0,0 +1,84 @@ + + + + + + Регистрация / Авторизация + + + + +
+

Сервис подписи файлов

+

Авторизация

+
+ +
+ + +
+ +
+ + +
+ + +
+
+ + + \ No newline at end of file diff --git a/web/register_form.html b/web/register_form.html index 22dc18e..023df90 100644 --- a/web/register_form.html +++ b/web/register_form.html @@ -96,7 +96,11 @@ - + + +
+
+