Setting up stub auth service
This commit is contained in:
parent
2a64b5761e
commit
c0a58ff6b6
36
authentication-service/cmd/api/main.go
Normal file
36
authentication-service/cmd/api/main.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"authentication/data"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const webPort = "4000"
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
DB *sql.DB
|
||||||
|
Models data.Models
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// TODO connect to DB
|
||||||
|
|
||||||
|
// set up config
|
||||||
|
app := Config{}
|
||||||
|
|
||||||
|
log.Printf("Starting authentication service on port %s\n", webPort)
|
||||||
|
|
||||||
|
// define http server
|
||||||
|
srv := &http.Server{
|
||||||
|
Addr: fmt.Sprintf(":%s", webPort),
|
||||||
|
Handler: app.routes(),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := srv.ListenAndServe()
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
}
|
27
authentication-service/cmd/api/routes.go
Normal file
27
authentication-service/cmd/api/routes.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
"github.com/go-chi/cors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (app *Config) routes() http.Handler {
|
||||||
|
mux := chi.NewRouter()
|
||||||
|
|
||||||
|
// specify who is allowed to connect
|
||||||
|
mux.Use(cors.Handler(cors.Options{
|
||||||
|
AllowedOrigins: []string{"https://*", "http://*"},
|
||||||
|
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||||||
|
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
|
||||||
|
ExposedHeaders: []string{"Link"},
|
||||||
|
AllowCredentials: true,
|
||||||
|
MaxAge: 300,
|
||||||
|
}))
|
||||||
|
|
||||||
|
mux.Use(middleware.Heartbeat("/ping"))
|
||||||
|
|
||||||
|
return mux
|
||||||
|
}
|
264
authentication-service/data/models.go
Normal file
264
authentication-service/data/models.go
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
const dbTimeout = time.Second * 3
|
||||||
|
|
||||||
|
var db *sql.DB
|
||||||
|
|
||||||
|
// New is the function used to create an instance of the data package. It returns the type
|
||||||
|
// Model, which embeds all the types we want to be available to our application.
|
||||||
|
func New(dbPool *sql.DB) Models {
|
||||||
|
db = dbPool
|
||||||
|
|
||||||
|
return Models{
|
||||||
|
User: User{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Models is the type for this package. Note that any model that is included as a member
|
||||||
|
// in this type is available to us throughout the application, anywhere that the
|
||||||
|
// app variable is used, provided that the model is also added in the New function.
|
||||||
|
type Models struct {
|
||||||
|
User User
|
||||||
|
}
|
||||||
|
|
||||||
|
// User is the structure which holds one user from the database.
|
||||||
|
type User struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
FirstName string `json:"first_name,omitempty"`
|
||||||
|
LastName string `json:"last_name,omitempty"`
|
||||||
|
Password string `json:"-"`
|
||||||
|
Active int `json:"active"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll returns a slice of all users, sorted by last name
|
||||||
|
func (u *User) GetAll() ([]*User, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
query := `select id, email, first_name, last_name, password, user_active, created_at, updated_at
|
||||||
|
from users order by last_name`
|
||||||
|
|
||||||
|
rows, err := db.QueryContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var users []*User
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var user User
|
||||||
|
err := rows.Scan(
|
||||||
|
&user.ID,
|
||||||
|
&user.Email,
|
||||||
|
&user.FirstName,
|
||||||
|
&user.LastName,
|
||||||
|
&user.Password,
|
||||||
|
&user.Active,
|
||||||
|
&user.CreatedAt,
|
||||||
|
&user.UpdatedAt,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error scanning", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
users = append(users, &user)
|
||||||
|
}
|
||||||
|
|
||||||
|
return users, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByEmail returns one user by email
|
||||||
|
func (u *User) GetByEmail(email string) (*User, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
query := `select id, email, first_name, last_name, password, user_active, created_at, updated_at from users where email = $1`
|
||||||
|
|
||||||
|
var user User
|
||||||
|
row := db.QueryRowContext(ctx, query, email)
|
||||||
|
|
||||||
|
err := row.Scan(
|
||||||
|
&user.ID,
|
||||||
|
&user.Email,
|
||||||
|
&user.FirstName,
|
||||||
|
&user.LastName,
|
||||||
|
&user.Password,
|
||||||
|
&user.Active,
|
||||||
|
&user.CreatedAt,
|
||||||
|
&user.UpdatedAt,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOne returns one user by id
|
||||||
|
func (u *User) GetOne(id int) (*User, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
query := `select id, email, first_name, last_name, password, user_active, created_at, updated_at from users where id = $1`
|
||||||
|
|
||||||
|
var user User
|
||||||
|
row := db.QueryRowContext(ctx, query, id)
|
||||||
|
|
||||||
|
err := row.Scan(
|
||||||
|
&user.ID,
|
||||||
|
&user.Email,
|
||||||
|
&user.FirstName,
|
||||||
|
&user.LastName,
|
||||||
|
&user.Password,
|
||||||
|
&user.Active,
|
||||||
|
&user.CreatedAt,
|
||||||
|
&user.UpdatedAt,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update updates one user in the database, using the information
|
||||||
|
// stored in the receiver u
|
||||||
|
func (u *User) Update() error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
stmt := `update users set
|
||||||
|
email = $1,
|
||||||
|
first_name = $2,
|
||||||
|
last_name = $3,
|
||||||
|
user_active = $4,
|
||||||
|
updated_at = $5
|
||||||
|
where id = $6
|
||||||
|
`
|
||||||
|
|
||||||
|
_, err := db.ExecContext(ctx, stmt,
|
||||||
|
u.Email,
|
||||||
|
u.FirstName,
|
||||||
|
u.LastName,
|
||||||
|
u.Active,
|
||||||
|
time.Now(),
|
||||||
|
u.ID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes one user from the database, by User.ID
|
||||||
|
func (u *User) Delete() error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
stmt := `delete from users where id = $1`
|
||||||
|
|
||||||
|
_, err := db.ExecContext(ctx, stmt, u.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteByID deletes one user from the database, by ID
|
||||||
|
func (u *User) DeleteByID(id int) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
stmt := `delete from users where id = $1`
|
||||||
|
|
||||||
|
_, err := db.ExecContext(ctx, stmt, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert inserts a new user into the database, and returns the ID of the newly inserted row
|
||||||
|
func (u *User) Insert(user User) (int, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), 12)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var newID int
|
||||||
|
stmt := `insert into users (email, first_name, last_name, password, user_active, created_at, updated_at)
|
||||||
|
values ($1, $2, $3, $4, $5, $6, $7) returning id`
|
||||||
|
|
||||||
|
err = db.QueryRowContext(ctx, stmt,
|
||||||
|
user.Email,
|
||||||
|
user.FirstName,
|
||||||
|
user.LastName,
|
||||||
|
hashedPassword,
|
||||||
|
user.Active,
|
||||||
|
time.Now(),
|
||||||
|
time.Now(),
|
||||||
|
).Scan(&newID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetPassword is the method we will use to change a user's password.
|
||||||
|
func (u *User) ResetPassword(password string) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 12)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt := `update users set password = $1 where id = $2`
|
||||||
|
_, err = db.ExecContext(ctx, stmt, hashedPassword, u.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PasswordMatches uses Go's bcrypt package to compare a user supplied password
|
||||||
|
// with the hash we have stored for a given user in the database. If the password
|
||||||
|
// and hash match, we return true; otherwise, we return false.
|
||||||
|
func (u *User) PasswordMatches(plainText string) (bool, error) {
|
||||||
|
err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(plainText))
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, bcrypt.ErrMismatchedHashAndPassword):
|
||||||
|
// invalid password
|
||||||
|
return false, nil
|
||||||
|
default:
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
9
authentication-service/go.mod
Normal file
9
authentication-service/go.mod
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
module authentication
|
||||||
|
|
||||||
|
go 1.22.5
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-chi/chi/v5 v5.1.0
|
||||||
|
github.com/go-chi/cors v1.2.1
|
||||||
|
golang.org/x/crypto v0.26.0
|
||||||
|
)
|
6
authentication-service/go.sum
Normal file
6
authentication-service/go.sum
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
||||||
|
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
|
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||||
|
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||||
|
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||||
|
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
Loading…
Reference in New Issue
Block a user