Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Dockerfile.auth
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ WORKDIR /app

COPY go.work go.work.sum ./
COPY auth/go.mod auth/go.sum ./auth/
COPY profiles/go.mod profiles/go.sum ./profiles/
COPY gateway/go.mod gateway/go.sum ./gateway/
COPY shared/go.mod shared/go.sum ./shared/
COPY protos/go.mod protos/go.sum ./protos/
Expand Down
1 change: 1 addition & 0 deletions Dockerfile.gateway
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ WORKDIR /app

COPY go.work go.work.sum ./
COPY auth/go.mod auth/go.sum ./auth/
COPY profiles/go.mod profiles/go.sum ./profiles/
COPY gateway/go.mod gateway/go.sum ./gateway/
COPY shared/go.mod shared/go.sum ./shared/
COPY protos/go.mod protos/go.sum ./protos/
Expand Down
33 changes: 33 additions & 0 deletions Dockerfile.profiles
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
FROM golang:1.25.0-alpine AS builder

RUN apk add --no-cache git

WORKDIR /app

COPY go.work go.work.sum ./
COPY auth/go.mod auth/go.sum ./auth/
COPY profiles/go.mod profiles/go.sum ./profiles/
COPY gateway/go.mod gateway/go.sum ./gateway/
COPY shared/go.mod shared/go.sum ./shared/
COPY protos/go.mod protos/go.sum ./protos/

RUN go work sync && go mod download

COPY profiles/ ./profiles/
COPY shared/ ./shared/
COPY protos/ ./protos/

RUN go build -o ./profiles/profiles ./profiles/cmd/profiles/main.go

FROM alpine:3.22.1

RUN apk add --no-cache ca-certificates

WORKDIR /app

COPY --from=builder /app/profiles/profiles .
COPY --from=builder /app/profiles/migrations ./migrations

EXPOSE 50051

CMD ["./profiles"]
7 changes: 7 additions & 0 deletions Taskfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ tasks:
- |
echo "Build Auth Image..."
docker build -f Dockerfile.auth -t got-auth:latest .
build_profiles:
dir: "{{.TASKFILE_DIR}}"
desc: Build Profiles image
cmds:
- |
echo "Build Profiles Image..."
docker build -f Dockerfile.profiles -t got-profiles:latest .
build_gateway:
dir: "{{.TASKFILE_DIR}}"
desc: Build Gateway image
Expand Down
4 changes: 2 additions & 2 deletions auth/cmd/auth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"github.com/alexwatcher/gateofthings/auth/internal/app"
"github.com/alexwatcher/gateofthings/auth/internal/config"
"github.com/alexwatcher/gateofthings/auth/internal/consts"
"github.com/alexwatcher/gateofthings/auth/internal/migrator/postgresql"
sharedpgsql "github.com/alexwatcher/gateofthings/shared/pkg/migrator/postgresql"
"github.com/alexwatcher/gateofthings/shared/pkg/telemetry"
)

Expand All @@ -24,7 +24,7 @@ func main() {
telemetry.MustInitMeter(context.Background(), res, cfg.Telemetry.MetricsEndpoint)

slog.Info("start migration")
postgresql.Migrate(cfg.Database)
sharedpgsql.Migrate(cfg.Database)
slog.Info("end migration")

slog.Info("starting application")
Expand Down
7 changes: 4 additions & 3 deletions auth/internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import (
"time"

grpcauth "github.com/alexwatcher/gateofthings/auth/internal/grpc/auth"
"github.com/alexwatcher/gateofthings/auth/internal/grpc/interceptors/tracing"
"github.com/alexwatcher/gateofthings/auth/internal/grpc/interceptors/valid"
"github.com/alexwatcher/gateofthings/auth/internal/repository/postgresql"
"github.com/alexwatcher/gateofthings/auth/internal/services"
"github.com/alexwatcher/gateofthings/shared/pkg/config"
"github.com/alexwatcher/gateofthings/shared/pkg/grpc/interceptors/tracing"
"github.com/alexwatcher/gateofthings/shared/pkg/grpc/interceptors/valid"
sharedpgsql "github.com/alexwatcher/gateofthings/shared/pkg/repository/postgresql"
"google.golang.org/grpc"
)

Expand All @@ -26,7 +27,7 @@ type App struct {
// service with the server and returns the configured App instance.
func New(ctx context.Context, gRPConfig config.GRPCSrvConfig, dbConfig config.DatabaseConfig, secret string, tokenTTL time.Duration) *App {

dbConn, err := postgresql.NewConnection(ctx, dbConfig)
dbConn, err := sharedpgsql.NewConnection(ctx, dbConfig)
if err != nil {
slog.Error("failed to connect to database", "error", err)
panic(err)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
package valid
package auth

import (
authv1 "github.com/alexwatcher/gateofthings/protos/gen/go/auth/v1"
sharedvalid "github.com/alexwatcher/gateofthings/shared/pkg/grpc/interceptors/valid"
validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/go-ozzo/ozzo-validation/v4/is"
)

func init() {
rulesMap[getStructId(&authv1.SignUpRequest{})] = func(s any) error {

sharedvalid.RegisterRule(&authv1.SignUpRequest{}, func(s any) error {
rr := s.(*authv1.SignUpRequest)
return validation.ValidateStruct(rr,
validation.Field(&rr.Email, validation.Required, is.EmailFormat),
validation.Field(&rr.Password, validation.Required, validation.Length(6, 128)),
)
}
})

rulesMap[getStructId(&authv1.SignInRequest{})] = func(s any) error {
sharedvalid.RegisterRule(&authv1.SignInRequest{}, func(s any) error {
lr := s.(*authv1.SignInRequest)
return validation.ValidateStruct(lr,
validation.Field(&lr.Email, validation.Required, is.EmailFormat),
validation.Field(&lr.Password, validation.Required, validation.Length(6, 128)),
)
}
})
}
7 changes: 0 additions & 7 deletions auth/internal/repository/postgresql/constants.go

This file was deleted.

9 changes: 5 additions & 4 deletions auth/internal/repository/postgresql/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/Masterminds/squirrel"
"github.com/alexwatcher/gateofthings/auth/internal/models"
"github.com/alexwatcher/gateofthings/auth/internal/repository"
spgsql "github.com/alexwatcher/gateofthings/shared/pkg/repository/postgresql"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
Expand All @@ -26,7 +27,7 @@ func (r *UsersRepo) Insert(ctx context.Context, email string, passhash []byte) (

id := uuid.New()

sql, args, err := sqlBuilder.
sql, args, err := spgsql.SqlBuilder.
Insert("auth_users").
Columns("id", "email", "password_hash").
Values(id, email, passhash).
Expand All @@ -38,7 +39,7 @@ func (r *UsersRepo) Insert(ctx context.Context, email string, passhash []byte) (
_, err = r.conn.Exec(ctx, sql, args...)
if err != nil {
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) && pgErr.Code == errCodeUniqueViolation {
if errors.As(err, &pgErr) && pgErr.Code == spgsql.ErrCodeUniqueViolation {
return "", fmt.Errorf("%s: %w", op, repository.ErrUserAlreadyExists)
}
return "", fmt.Errorf("%s: %w", op, err)
Expand All @@ -48,10 +49,10 @@ func (r *UsersRepo) Insert(ctx context.Context, email string, passhash []byte) (
}

func (r *UsersRepo) Get(ctx context.Context, email string) (models.User, error) {
op := "repository.users.Select"
op := "repository.users.Get"

var user models.User
query, args, err := sqlBuilder.
query, args, err := spgsql.SqlBuilder.
Select("id", "email", "password_hash").
From("auth_users").
Where(squirrel.Eq{"email": email}).
Expand Down
10 changes: 0 additions & 10 deletions auth/migrations/001_init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,6 @@ CREATE TABLE IF NOT EXISTS auth_users (
);
CREATE INDEX idx_auth_users_email ON auth_users (email);

CREATE TABLE IF NOT EXISTS user_profiles (
id UUID PRIMARY KEY REFERENCES auth_users(id) ON DELETE CASCADE,
full_name TEXT,
permissions JSONB,
settings JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- +goose Down
DROP INDEX IF EXISTS idx_auth_users_email;
DROP TABLE IF EXISTS auth_users;
DROP TABLE IF EXISTS user_profiles;
2 changes: 2 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ services:
METRICS_PASS: metrics_pass
AUTH_USER: auth_user
AUTH_PASS: auth_pass
PROFILES_USER: profiles_user
PROFILES_PASS: profiles_pass
depends_on:
postgres:
condition: service_healthy
Expand Down
File renamed without changes.
1 change: 1 addition & 0 deletions gateway/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ require (
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/spec v0.20.6 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions gateway/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
Expand Down
9 changes: 5 additions & 4 deletions gateway/internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import (
"log/slog"
"net/http"

"github.com/alexwatcher/gateofthings/gateway/internal/interceptors"
"github.com/alexwatcher/gateofthings/gateway/internal/middlewares"
"github.com/alexwatcher/gateofthings/gateway/internal/grpc/interceptors"
"github.com/alexwatcher/gateofthings/gateway/internal/http/middlewares"
"github.com/alexwatcher/gateofthings/gateway/internal/openapi"
authv1 "github.com/alexwatcher/gateofthings/protos/gen/go/auth/v1"
"github.com/alexwatcher/gateofthings/shared/pkg/config"
Expand Down Expand Up @@ -42,15 +42,16 @@ func (a *App) MustRun(ctx context.Context) {
// server can't be started, it returns an error.
func (a *App) Run(ctx context.Context) error {
mux := runtime.NewServeMux(
runtime.WithForwardResponseOption(interceptors.MakeSetSignInCookie()),
runtime.WithForwardResponseOption(interceptors.SetSignInCookies),
runtime.WithMiddlewares(
middlewares.TracingMiddleware,
middlewares.MakeCSRFMiddleware([]string{"/v1/auth/signin", "/v1/auth/signup"}),
middlewares.MakeAuthTokenMiddleware([]string{"/v1/auth/signin", "/v1/auth/signup"}),
),
)

opts := []grpc.DialOption{
grpc.WithChainUnaryInterceptor(interceptors.MakeTracingClientInterceptor()),
grpc.WithChainUnaryInterceptor(interceptors.TracingClientInterceptor),
grpc.WithTransportCredentials(insecure.NewCredentials()),
}
err := authv1.RegisterAuthHandlerFromEndpoint(ctx, mux, a.authConfig.Address, opts)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
func MakeSetSignInCookie() func(ctx context.Context, w http.ResponseWriter, resp proto.Message) error {
return func(ctx context.Context, w http.ResponseWriter, resp proto.Message) error {
if r, ok := resp.(*authv1.SignInResponse); ok {

http.SetCookie(w, &http.Cookie{
Name: "token",
Value: r.Token,
Expand Down
33 changes: 33 additions & 0 deletions gateway/internal/grpc/interceptors/signincookies.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package interceptors

import (
"context"
"net/http"

authv1 "github.com/alexwatcher/gateofthings/protos/gen/go/auth/v1"
"github.com/google/uuid"
"google.golang.org/protobuf/proto"
)

func SetSignInCookies(ctx context.Context, w http.ResponseWriter, resp proto.Message) error {
if r, ok := resp.(*authv1.SignInResponse); ok {
http.SetCookie(w, &http.Cookie{
Name: "token",
Value: r.Token,
Path: "/",
HttpOnly: true,
Secure: true,
})

csrfToken := uuid.NewString()
w.Header().Set("X-CSRF-Token", csrfToken)
http.SetCookie(w, &http.Cookie{
Name: "csrf_token",
Value: csrfToken,
Path: "/",
HttpOnly: false,
Secure: true,
})
}
return nil
}
22 changes: 22 additions & 0 deletions gateway/internal/grpc/interceptors/tracing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package interceptors

import (
"context"

"github.com/alexwatcher/gateofthings/shared/pkg/telemetry/propagation"
"go.opentelemetry.io/otel"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)

func TracingClientInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
md, ok := metadata.FromOutgoingContext(ctx)
if ok {
md = md.Copy()
} else {
md = metadata.New(nil)
}
otel.GetTextMapPropagator().Inject(ctx, propagation.GRPCMetadataCarrier(md))
ctx = metadata.NewOutgoingContext(ctx, md)
return invoker(ctx, method, req, reply, cc, opts...)
}
51 changes: 51 additions & 0 deletions gateway/internal/http/middlewares/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package middlewares

import (
"net/http"

"github.com/golang-jwt/jwt/v5"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
)

func MakeAuthTokenMiddleware(ignorePaths []string) func(next runtime.HandlerFunc) runtime.HandlerFunc {
return func(next runtime.HandlerFunc) runtime.HandlerFunc {
ignorePathMap := make(map[string]struct{}, len(ignorePaths))
for _, path := range ignorePaths {
ignorePathMap[path] = struct{}{}
}

return func(w http.ResponseWriter, r *http.Request, pathParams map[string]string) {
if _, ok := ignorePathMap[r.URL.Path]; !ok {
userId := ""
cookies := r.CookiesNamed("token")
if len(cookies) > 0 && len(cookies[0].Value) > 0 {
token := cookies[0].Value

jwtToken, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
return nil, nil
})

if err != nil || !jwtToken.Valid {
// remove token
http.SetCookie(w, &http.Cookie{
Name: "token",
MaxAge: 0,
})
http.Error(w, "invalid token", http.StatusForbidden)
return
}

claims, ok := jwtToken.Claims.(jwt.MapClaims)
if ok {
uid, ok := claims["uid"].(string)
if ok {
userId = uid
}
}
}
r.Header.Set("X-User-ID", userId)
}
next(w, r, pathParams)
}
}
}
Loading
Loading