feat: Added chapter 6 first auth attempt

This commit is contained in:
syrell 2024-10-24 22:40:26 +02:00
parent 9603509fcd
commit e72fd2ee86
10 changed files with 125 additions and 17 deletions

1
go.mod
View File

@ -6,4 +6,5 @@ require (
github.com/google/uuid v1.6.0 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/lib/pq v1.10.9 // indirect
golang.org/x/crypto v0.28.0 // indirect
)

2
go.sum
View File

@ -4,3 +4,5 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=

12
internal/auth/auth.go Normal file
View File

@ -0,0 +1,12 @@
package auth
import "golang.org/x/crypto/bcrypt"
func HashPassword(password string) (string, error) {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 10)
return string(hashedPassword), err
}
func CheckPasswordHash(password, hash string) error {
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
}

View File

@ -19,8 +19,9 @@ type Chirp struct {
}
type User struct {
ID uuid.UUID
CreatedAt time.Time
UpdatedAt time.Time
Email string
ID uuid.UUID
CreatedAt time.Time
UpdatedAt time.Time
Email string
HashedPassword string
}

View File

@ -10,31 +10,33 @@ import (
)
const createUser = `-- name: CreateUser :one
INSERT INTO users (id, created_at, updated_at, email)
INSERT INTO users (id, created_at, updated_at, email, hashed_password)
VALUES (
gen_random_uuid(),
NOW(),
NOW(),
$1
$1,
$2
)
RETURNING id, created_at, updated_at, email
RETURNING id, created_at, updated_at, email, hashed_password
`
func (q *Queries) CreateUser(ctx context.Context, email string) (User, error) {
row := q.db.QueryRowContext(ctx, createUser, email)
func (q *Queries) CreateUser(ctx context.Context, email string, hashed_password string) (User, error) {
row := q.db.QueryRowContext(ctx, createUser, email, hashed_password)
var i User
err := row.Scan(
&i.ID,
&i.CreatedAt,
&i.UpdatedAt,
&i.Email,
&i.HashedPassword,
)
return i, err
}
const deleteUser = `-- name: DeleteUser :one
DELETE FROM users
RETURNING id, created_at, updated_at, email
RETURNING id, created_at, updated_at, email, hashed_password
`
func (q *Queries) DeleteUser(ctx context.Context) (User, error) {
@ -45,6 +47,25 @@ func (q *Queries) DeleteUser(ctx context.Context) (User, error) {
&i.CreatedAt,
&i.UpdatedAt,
&i.Email,
&i.HashedPassword,
)
return i, err
}
const getUserByEmail = `-- name: GetUserByEmail :one
SELECT id, created_at, updated_at, email, hashed_password FROM users
WHERE users.email = $1
`
func (q *Queries) GetUserByEmail(ctx context.Context, email string) (User, error) {
row := q.db.QueryRowContext(ctx, getUserByEmail, email)
var i User
err := row.Scan(
&i.ID,
&i.CreatedAt,
&i.UpdatedAt,
&i.Email,
&i.HashedPassword,
)
return i, err
}

51
login.go Normal file
View File

@ -0,0 +1,51 @@
package main
import (
"encoding/json"
"log"
"net/http"
"github.com/finchrelia/chirpy-server/internal/auth"
)
func (cfg *apiConfig) Login(w http.ResponseWriter, r *http.Request) {
type params struct {
Email string `json:"email"`
Password string `json:"password"`
}
decoder := json.NewDecoder(r.Body)
p := params{}
err := decoder.Decode(&p)
if err != nil {
log.Printf("Incorrect email or password")
w.WriteHeader(401)
return
}
loggedUser, err := cfg.DB.GetUserByEmail(r.Context(), p.Email)
if err != nil {
log.Printf("Error retrieving user: %s", err)
}
err = auth.CheckPasswordHash(p.Password, loggedUser.HashedPassword)
if err != nil {
log.Printf("Incorrect email or password")
w.WriteHeader(401)
return
}
data, err := json.Marshal(User{
ID: loggedUser.ID,
CreatedAt: loggedUser.CreatedAt,
UpdatedAt: loggedUser.UpdatedAt,
Email: loggedUser.Email,
})
if err != nil {
log.Printf("Error marshalling JSON: %s", err)
w.WriteHeader(500)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
w.Write(data)
}

View File

@ -51,6 +51,7 @@ func main() {
mux.HandleFunc("POST /api/chirps", apiCfg.chirpsCreate)
mux.HandleFunc("POST /api/users", apiCfg.createUsers)
mux.HandleFunc("GET /api/chirps/{chirpID}", apiCfg.getChirp)
mux.HandleFunc("POST /api/login", apiCfg.Login)
server := &http.Server{
Addr: ":8080",

View File

@ -11,3 +11,7 @@ RETURNING *;
-- name: DeleteUser :one
DELETE FROM users
RETURNING *;
-- name: GetUserByEmail :one
SELECT * FROM users
WHERE users.email = $1;

View File

@ -0,0 +1,7 @@
-- +goose Up
ALTER TABLE users
ADD COLUMN hashed_password TEXT DEFAULT 'unset' NOT NULL;
-- +goose Down
ALTER TABLE users
DROP COLUMN hashed_password;

View File

@ -6,19 +6,22 @@ import (
"net/http"
"time"
"github.com/finchrelia/chirpy-server/internal/auth"
"github.com/google/uuid"
)
type User struct {
ID uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Email string `json:"email"`
ID uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Email string `json:"email"`
HashedPassword string `json:"-"`
}
func (cfg *apiConfig) createUsers(w http.ResponseWriter, r *http.Request) {
type parameters struct {
Email string `json:"email"`
Email string `json:"email"`
Password string `json:"password"`
}
decoder := json.NewDecoder(r.Body)
@ -30,7 +33,12 @@ func (cfg *apiConfig) createUsers(w http.ResponseWriter, r *http.Request) {
return
}
defer r.Body.Close()
newDBUser, err := cfg.DB.CreateUser(r.Context(), params.Email)
hashedPassword, err := auth.HashPassword(params.Password)
if err != nil {
log.Printf("Error hashing password: %s", err)
}
newDBUser, err := cfg.DB.CreateUser(r.Context(), params.Email, hashedPassword)
if err != nil {
log.Printf("Error creating user %s: %s", params.Email, err)
w.WriteHeader(500)