Сайт на Golang. Авторизация. Часть 4

Заканчиваем очередной цикл простейшим примером регистрации пользователей на сайте. Для этого нам понадобятся:

  • страница с формой регистрации;
  • эндпоинт для приема данных с формы и выполнения какой-то логики;
  • метод, добавляющий пользователя в базу.

Начать предлагаю с конца — с создания метода для добавления нового пользователя в базу данных. Для этого в файле user.go добавьте следующий метод:

func (r *Repository) AddNewUser(ctx context.Context, name, surname, login, hashedPassword string) (err error) {
	_, err = r.pool.Exec(ctx, `insert into users (name, surname, login, hashed_password) values ($1, $2,$3, $4)`, name, surname, login, hashedPassword)
	if err != nil {
		err = fmt.Errorf("failed to exec data: %w", err)
		return
	}
	return
}

Здесь мы принимаем на вход имя, фамилию, логин и хеш пароля и пробуем добавить такого пользователя. Если вдруг у нас не получится (например, пользователь с таким логином уже существует), то мы вернем ошибку (кстати, за это отвечает сама БД: вспоминаем, что когда мы создавали табличку с пользователями, то для login мы прописали признак unique).

Теперь добавим новый эндпоинт, который будет направлять нас напрямую на страницу регистрации. Для этого идем в application.go и в методе Routes добавим следующее:

	r.GET("/signup", func(rw http.ResponseWriter, r *http.Request, p httprouter.Params) {
		a.SignupPage(rw, "")
	})

Теперь Routes должна выглядеть так:

func (a app) Routes(r *httprouter.Router) {
	r.ServeFiles("/public/*filepath", http.Dir("public"))
	r.GET("/", a.authorized(a.StartPage))
	r.GET("/login", func(rw http.ResponseWriter, r *http.Request, p httprouter.Params) {
		a.LoginPage(rw, "")
	})
	r.POST("/login", a.Login)
	r.GET("/logout", a.Logout)
	r.GET("/signup", func(rw http.ResponseWriter, r *http.Request, p httprouter.Params) {
		a.SignupPage(rw, "")
	})
}

Добавим соответствующий метод:

func (a app) SignupPage(rw http.ResponseWriter, message string) {
	sp := filepath.Join("public", "html", "signup.html")
	tmpl, err := template.ParseFiles(sp)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusBadRequest)
		return
	}
	type answer struct {
		Message string
	}
	data := answer{message}
	err = tmpl.ExecuteTemplate(rw, "signup", data)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusBadRequest)
		return
	}
}

И обязательно html шаблон (public/html/signup.html):

{{define "signup"}}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Sign up page</title>
</head>
<body>
<form id="loginForm" name="signUpForm" action="/signup" method="post" >
    <label for="name">Имя</label><br>
    <input type="text" id="name" name="name" required><br>
    <label for="surname">Фамилия</label><br>
    <input type="text" id="surname" name="surname" required><br>
    <label for="login">Логин</label><br>
    <input type="login" id="login" name="login" required><br>
    <label for="password">Пароль</label><br>
    <input type="password" id="password" name="password" required><br>
    <label for="password2">Повторите пароль</label><br>
    <input type="password" id="password2" name="password2" required><br>
    <br>
    <button type="submit" name="submitBtn">Зарегистрироваться</button>
</form>
{{if . }}
<div>
    {{.Message}}
</div>
{{end}}
</body>
</html>
{{end}}

Запустим через go run main.go и перейдем на http://localhost:8080/signup

Теперь в application.go нужно добавить эндпоинт для приема данных с формы и метод-хендлер, который будет отвечать за внутреннюю логику.

В Routes добавляем r.POST(«/signup», a.Signup):

func (a app) Routes(r *httprouter.Router) {
	r.ServeFiles("/public/*filepath", http.Dir("public"))
	r.GET("/", a.authorized(a.StartPage))
	r.GET("/login", func(rw http.ResponseWriter, r *http.Request, p httprouter.Params) {
		a.LoginPage(rw, "")
	})
	r.POST("/login", a.Login)
	r.GET("/logout", a.Logout)
	r.GET("/signup", func(rw http.ResponseWriter, r *http.Request, p httprouter.Params) {
		a.SignupPage(rw, "")
	})
	r.POST("/signup", a.Signup)
}

Добавляем метод-хендлер:

func (a app) Signup(rw http.ResponseWriter, r *http.Request, p httprouter.Params) {
	name := strings.TrimSpace(r.FormValue("name"))
	surname := strings.TrimSpace(r.FormValue("surname"))
	login := strings.TrimSpace(r.FormValue("login"))
	password := strings.TrimSpace(r.FormValue("password"))
	password2 := strings.TrimSpace(r.FormValue("password2"))
	if name == "" || surname == "" || login == "" || password == "" {
		a.SignupPage(rw, "Все поля должны быть заполнены!")
		return
	}
	if password != password2 {
		a.SignupPage(rw, "Пароли не совпадают! Попробуйте еще")
		return
	}
	hash := md5.Sum([]byte(password))
	hashedPass := hex.EncodeToString(hash[:])
	err := a.repo.AddNewUser(a.ctx, name, surname, login, hashedPass)
	if err != nil {
		a.SignupPage(rw, fmt.Sprintf("Ошибка создания пользователя: %v", err))
		return
	}
	a.LoginPage(rw, fmt.Sprintf("%s, вы успешно зарегистрированы! Теперь вам доступен вход через страницу авторизации", name))
}

Что у нас здесь за портянка с кодом:

  • получаем данные с формы;
  • проверяем, что они не пустые;
  • сравниваем пароли;
  • генерируем хеш пароля;
  • записываем нового пользователя в базу;

Если что-то идет не так, мы отправляем нашего гостя обратно с сообщением, в чем ошибка. Ну а если, все хорошо, то поздравляем нового пользователя с регистрацией и перенаправляем его на страницу авторизации.

Для удобства добавим на странице авторизации (файл login.html) ссылку на нашу новую страницу и приведем формы к единообразному виду:

{{define "login"}}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login page</title>
</head>
<body>
<form id="loginForm" name="loginForm" action="/login" method="post" class="mt-4">
    <label for="login">Логин</label><br>
    <input type="login" id="login" name="login" autocomplete="on"><br>
    <label for="password">Пароль</label><br>
    <input type="password" id="password" name="password" autocomplete="on"><br>
    <br>
    <button type="submit" name="submitBtn">Войти</button>
</form>
{{if . }}
<div>
    {{.Message}}
</div>
{{end}}
<br>
<a href="/signup">Зарегистрироваться</a>
</body>
</html>
{{end}}

Проверяем:

Проверяем в базе, что пользователь действительно создался.

Структура проекта:

Ссылка на pull request: https://github.com/alextonkonogov/atonko-authorization/pull/4

Прошу предложенное решение рассматривать исключительно в образовательных целях. Мы могли бы много чего в нем улучшить и сделать более безопасным, однако это бы только усложнило и без того непростой материал. Очень надеюсь, что статьи с моими уроками окажутся для кого-то полезными. Всем добра 🙂

2 комментария

Leave a Comment

Ваш адрес email не будет опубликован.