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/google/uuid v1.6.0 // indirect
github.com/joho/godotenv v1.5.1 // indirect github.com/joho/godotenv v1.5.1 // indirect
github.com/lib/pq v1.10.9 // 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/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 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 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 { type User struct {
ID uuid.UUID ID uuid.UUID
CreatedAt time.Time CreatedAt time.Time
UpdatedAt time.Time UpdatedAt time.Time
Email string Email string
HashedPassword string
} }

View File

@ -10,31 +10,33 @@ import (
) )
const createUser = `-- name: CreateUser :one 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 ( VALUES (
gen_random_uuid(), gen_random_uuid(),
NOW(), NOW(),
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) { func (q *Queries) CreateUser(ctx context.Context, email string, hashed_password string) (User, error) {
row := q.db.QueryRowContext(ctx, createUser, email) row := q.db.QueryRowContext(ctx, createUser, email, hashed_password)
var i User var i User
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.Email, &i.Email,
&i.HashedPassword,
) )
return i, err return i, err
} }
const deleteUser = `-- name: DeleteUser :one const deleteUser = `-- name: DeleteUser :one
DELETE FROM users 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) { 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.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.Email, &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 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/chirps", apiCfg.chirpsCreate)
mux.HandleFunc("POST /api/users", apiCfg.createUsers) mux.HandleFunc("POST /api/users", apiCfg.createUsers)
mux.HandleFunc("GET /api/chirps/{chirpID}", apiCfg.getChirp) mux.HandleFunc("GET /api/chirps/{chirpID}", apiCfg.getChirp)
mux.HandleFunc("POST /api/login", apiCfg.Login)
server := &http.Server{ server := &http.Server{
Addr: ":8080", Addr: ":8080",

View File

@ -11,3 +11,7 @@ RETURNING *;
-- name: DeleteUser :one -- name: DeleteUser :one
DELETE FROM users DELETE FROM users
RETURNING *; 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" "net/http"
"time" "time"
"github.com/finchrelia/chirpy-server/internal/auth"
"github.com/google/uuid" "github.com/google/uuid"
) )
type User struct { type User struct {
ID uuid.UUID `json:"id"` ID uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
Email string `json:"email"` Email string `json:"email"`
HashedPassword string `json:"-"`
} }
func (cfg *apiConfig) createUsers(w http.ResponseWriter, r *http.Request) { func (cfg *apiConfig) createUsers(w http.ResponseWriter, r *http.Request) {
type parameters struct { type parameters struct {
Email string `json:"email"` Email string `json:"email"`
Password string `json:"password"`
} }
decoder := json.NewDecoder(r.Body) decoder := json.NewDecoder(r.Body)
@ -30,7 +33,12 @@ func (cfg *apiConfig) createUsers(w http.ResponseWriter, r *http.Request) {
return return
} }
defer r.Body.Close() 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 { if err != nil {
log.Printf("Error creating user %s: %s", params.Email, err) log.Printf("Error creating user %s: %s", params.Email, err)
w.WriteHeader(500) w.WriteHeader(500)