Signed-off-by: Nikolai Rodionov <iam@allanger.xyz>
This commit is contained in:
2
.env.dev
Normal file
2
.env.dev
Normal file
@@ -0,0 +1,2 @@
|
||||
export SOFTPLAYER_DB_CONNECTION_STRING=postgres://softplayer:qwertyu9@localhost:30432/softplayer?sslmode=disable
|
||||
export SOFYTPLAYER_REDIS_HOST=localhost:30379
|
||||
40
Taskfile.yml
Normal file
40
Taskfile.yml
Normal file
@@ -0,0 +1,40 @@
|
||||
# yaml-language-server: $schema=https://taskfile.dev/schema.json
|
||||
|
||||
version: "3"
|
||||
|
||||
tasks:
|
||||
build:
|
||||
desc: Build go code
|
||||
cmd: go build
|
||||
silent: true
|
||||
|
||||
run-migrations-dev:
|
||||
desc: Execute database migrations
|
||||
env:
|
||||
SOFTPLAYER_DB_CONNECTION_STRING: postgres://softplayer:qwertyu9@localhost:30432/softplayer?sslmode=disable
|
||||
cmd: go run main.go migrate --migrations-path=file://migrations
|
||||
|
||||
run-server-dev:
|
||||
desc: Run the local dev server
|
||||
env:
|
||||
SOFTPLAYER_DB_CONNECTION_STRING: postgres://softplayer:qwertyu9@localhost:30432/softplayer?sslmode=disable
|
||||
SOFTPLAYER_REDIS_HOST: localhost:30379
|
||||
cmd: go run main.go serve --dev-mode --reflection
|
||||
|
||||
deploy-local-env:
|
||||
desc: Run a kind cluster and deploy deps
|
||||
deps:
|
||||
- kind-cluster
|
||||
- helmfile-deploy
|
||||
|
||||
kind-cluster:
|
||||
desc: Run a kind cluster
|
||||
cmd: kind create cluster --config ./kind-config.yaml
|
||||
|
||||
kind-cluster-remove:
|
||||
desc: Remove the kind cluster
|
||||
cmd: kind delete cluster
|
||||
|
||||
helmfile-deploy:
|
||||
desc: Deploy the helmfile for the local dev
|
||||
cmd: helmfile apply
|
||||
@@ -46,7 +46,7 @@ func (a *AccountsNoAuthServer) SignUp(ctx context.Context, in *accounts.SignUpRe
|
||||
return nil, status.Error(codes.Aborted, "Couldn't generate Access Token")
|
||||
}
|
||||
|
||||
refreshToken, err := a.ctrl.GenerateRefreshToken(uuid)
|
||||
refreshToken, err := a.ctrl.GenerateRefreshToken(ctx, uuid)
|
||||
if err != nil {
|
||||
log.Error(err, "Couldn't generate a refresh token")
|
||||
return nil, status.Error(codes.Aborted, "Couldn't generate Access Token")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
environments:
|
||||
default:
|
||||
kubeContext: kind-kind
|
||||
values:
|
||||
- databases:
|
||||
postgres:
|
||||
|
||||
@@ -3,6 +3,7 @@ package controllers
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/helpers/hash"
|
||||
@@ -17,7 +18,7 @@ type AccountController struct {
|
||||
DevMode bool
|
||||
HashCost int16
|
||||
AccessTokenTTL time.Duration
|
||||
RefrestTokenTTL time.Duration
|
||||
RefreshTokenTTL time.Duration
|
||||
JWTSecret []byte
|
||||
}
|
||||
|
||||
@@ -62,15 +63,19 @@ func (c *AccountController) GenerateAccessToken(userID string) (string, error) {
|
||||
return token.SignedString(c.JWTSecret)
|
||||
}
|
||||
|
||||
func (c *AccountController) GenerateRefreshToken(userID string) (string, error) {
|
||||
func (c *AccountController) GenerateRefreshToken(ctx context.Context, userID string) (string, error) {
|
||||
tokenID := uuid.New().String()
|
||||
claims := jwt.MapClaims{
|
||||
"user_id": userID,
|
||||
"token_id": tokenID,
|
||||
"type": "refresh",
|
||||
"exp": time.Now().Add(c.RefrestTokenTTL).Unix(),
|
||||
"exp": time.Now().Add(c.RefreshTokenTTL).Unix(),
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
redisKey := fmt.Sprintf("refresh_token:%s", tokenID)
|
||||
if err := c.Redis.Set(ctx, redisKey, userID, c.RefreshTokenTTL).Err(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return token.SignedString(c.JWTSecret)
|
||||
}
|
||||
|
||||
178
main.go
178
main.go
@@ -6,52 +6,54 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
v1 "gitea.badhouseplants.net/softplayer/softplayer-backend/api/v1"
|
||||
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers"
|
||||
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/helpers/email"
|
||||
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/tools/logger"
|
||||
accounts "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/accounts/v1"
|
||||
applications_proto "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/applications/v1"
|
||||
email_proto "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/email/v1"
|
||||
"gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/environments/v1"
|
||||
"github.com/alecthomas/kong"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
"github.com/golang-migrate/migrate/v4/database/postgres"
|
||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||
grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/reflection"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type Serve struct {
|
||||
Port int64 `short:"p" env:"SOFTPLAYER_PORT" default:"4020"`
|
||||
Host string `env:"SOFTPLAYER_HOST" default:"0.0.0.0"`
|
||||
HashCost int16 `env:"SOFTPLAYER_HASH_COST" default:"1"`
|
||||
Reflection bool `env:"SOFTPLAYER_REFLECTION" default:"false"`
|
||||
DevMode bool `env:"SOFTPLAYER_DEV_MODE" default:"false"`
|
||||
SmtpHost string `env:"SOFTPLAYER_SMTP_HOST"`
|
||||
SmtpPort string `env:"SOFTPLAYER_SMTP_PORT" default:"587"`
|
||||
SmtpFrom string `env:"SOFTPLAYER_SMTP_FROM" default:"overlord@badhouseplants.net"`
|
||||
SmtpPassword string `env:"SOFTPLAYER_SMTP_PASSWORD"`
|
||||
DownloadDir string `env:"SOFTPLAYER_DOWNLOAD_DIR" default:"/tmp/softplayer"`
|
||||
DBConnectionString string `env:"SOFTPLAYER_DB_CONNECTION_STRING"`
|
||||
RedisHost string `env:"SOFTPLAYER_REDIS_HOST"`
|
||||
RefrestTokenTTL time.Duration `default:"8h"`
|
||||
AccessTokenTTL time.Duration `default:"15m"`
|
||||
JWTSecret string `default:"qwertyu9"`
|
||||
var CLI struct {
|
||||
Serve Serve `cmd:"" help:"Start the grpc server"`
|
||||
Migrate Migrate `cmd:"" help:"Run the database migrations"`
|
||||
}
|
||||
|
||||
var CLI struct {
|
||||
Serve Serve `cmd:"" help:"Start the grpc server"`
|
||||
type Serve struct {
|
||||
// Service related
|
||||
Port int64 `short:"p" env:"SOFTPLAYER_PORT" default:"4020"`
|
||||
Host string `env:"SOFTPLAYER_HOST" default:"0.0.0.0"`
|
||||
HashCost int16 `env:"SOFTPLAYER_HASH_COST" default:"1"`
|
||||
// SMTP Config
|
||||
SMTPHost string `env:"SOFTPLAYER_SMTP_HOST"`
|
||||
SMTPPort string `env:"SOFTPLAYER_SMTP_PORT" default:"587"`
|
||||
SMTPFrom string `env:"SOFTPLAYER_SMTP_FROM" default:"overlord@badhouseplants.net"`
|
||||
SMTPPassword string `env:"SOFTPLAYER_SMTP_PASSWORD"`
|
||||
// Database and redis
|
||||
DBConnectionString string `env:"SOFTPLAYER_DB_CONNECTION_STRING"`
|
||||
RedisHost string `env:"SOFTPLAYER_REDIS_HOST"`
|
||||
// JWT parameters
|
||||
RefrestTokenTTL time.Duration `default:"8h"`
|
||||
AccessTokenTTL time.Duration `default:"15m"`
|
||||
JWTSecret string `default:"qwertyu9"`
|
||||
// Dev and logging
|
||||
Reflection bool `env:"SOFTPLAYER_REFLECTION" default:"false"`
|
||||
DevMode bool `env:"SOFTPLAYER_DEV_MODE" default:"false"`
|
||||
}
|
||||
|
||||
type Migrate struct {
|
||||
DBConnectionString string `env:"SOFTPLAYER_DB_CONNECTION_STRING"`
|
||||
MigrationsPath string `env:"SOFTPLAYER_DB_MIGRATIOON_PATH" default:"file://migrations"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -62,15 +64,25 @@ func main() {
|
||||
if err := server(ctx, CLI.Serve); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case "migrate":
|
||||
if err := migrateDB(ctx, CLI.Migrate); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
default:
|
||||
panic(kongCtx.Command())
|
||||
}
|
||||
}
|
||||
|
||||
func migrateDB(ctx context.Context, db *sql.DB) error {
|
||||
// Migrate the database to the latest version
|
||||
func migrateDB(ctx context.Context, params Migrate) error {
|
||||
log := logger.FromContext(ctx)
|
||||
|
||||
log.Info("Starting a database migration driver")
|
||||
db, err := sql.Open("postgres", params.DBConnectionString)
|
||||
if err != nil {
|
||||
log.Error(err, "Couldn't start a database driver")
|
||||
return err
|
||||
}
|
||||
|
||||
driver, err := postgres.WithInstance(db, &postgres.Config{})
|
||||
if err != nil {
|
||||
log.Error(err, "Couldn't start a database migration driver")
|
||||
@@ -79,7 +91,7 @@ func migrateDB(ctx context.Context, db *sql.DB) error {
|
||||
|
||||
log.Info("Preparing database migrations")
|
||||
m, err := migrate.NewWithDatabaseInstance(
|
||||
"file://migrations",
|
||||
params.MigrationsPath,
|
||||
"postgres", driver)
|
||||
if err != nil {
|
||||
log.Error(err, "Couldn't perform database migrations")
|
||||
@@ -87,7 +99,7 @@ func migrateDB(ctx context.Context, db *sql.DB) error {
|
||||
}
|
||||
|
||||
log.Info("Starting database migrations")
|
||||
err = m.Up() // or m.Steps(2) if you want to explicitly set the number of migrations to r
|
||||
err = m.Up()
|
||||
if err != nil {
|
||||
if errors.Is(err, migrate.ErrNoChange) {
|
||||
log.Info("Database is already up-to-date")
|
||||
@@ -100,93 +112,40 @@ func migrateDB(ctx context.Context, db *sql.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractToken(ctx context.Context) (string, error) {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("missing metadata")
|
||||
}
|
||||
|
||||
auth := md.Get("authorization")
|
||||
if len(auth) == 0 {
|
||||
return "", fmt.Errorf("missing auth token")
|
||||
}
|
||||
|
||||
token := strings.TrimPrefix(auth[0], "Bearer ")
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func AuthInterceptor(
|
||||
ctx context.Context,
|
||||
req interface{},
|
||||
info *grpc.UnaryServerInfo,
|
||||
handler grpc.UnaryHandler,
|
||||
) (interface{}, error) {
|
||||
if !strings.Contains(info.FullMethod, "NoAuth") {
|
||||
tokenString, err := extractToken(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
|
||||
// hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key")
|
||||
return []byte("qwertyu9"), nil
|
||||
}, jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if claims, ok := token.Claims.(jwt.MapClaims); ok {
|
||||
fmt.Println(claims["foo"], claims["nbf"])
|
||||
} else {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
return handler(ctx, req)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Run the grpc backend server
|
||||
func server(ctx context.Context, params Serve) error {
|
||||
// Make sure the download dir exists
|
||||
log := logger.FromContext(ctx)
|
||||
|
||||
if err := os.MkdirAll(params.DownloadDir, 0o777); err != nil {
|
||||
log.Error(err, "Coulnd't create a download dir")
|
||||
return err
|
||||
}
|
||||
|
||||
// Init a logger
|
||||
log.Info("Starting a kubernetes manager")
|
||||
controller, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{})
|
||||
if err != nil {
|
||||
log.Error(err, "Coulnd't start a kube manager")
|
||||
return err
|
||||
}
|
||||
//log.Info("Starting a kubernetes manager")
|
||||
//controller, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{})
|
||||
//if err != nil {
|
||||
// log.Error(err, "Coulnd't start a kube manager")
|
||||
// return err
|
||||
//}
|
||||
|
||||
// TODO: Handle the error
|
||||
go func() {
|
||||
if err := controller.Start(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
//go func() {
|
||||
// if err := controller.Start(ctx); err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
//}()
|
||||
|
||||
log.Info("Opening a database connection")
|
||||
|
||||
db, err := sql.Open("postgres", params.DBConnectionString)
|
||||
if err != nil {
|
||||
log.Error(err, "Couldn't start a database driver")
|
||||
return err
|
||||
}
|
||||
|
||||
if err := migrateDB(ctx, db); err != nil {
|
||||
log.Error(err, "Error occured while executing migrations")
|
||||
return err
|
||||
}
|
||||
emailConfig := email.EmailConf{
|
||||
From: params.SmtpFrom,
|
||||
Password: params.SmtpPassword,
|
||||
SmtpHost: params.SmtpHost,
|
||||
SmtpPort: params.SmtpPort,
|
||||
}
|
||||
//emailConfig := email.EmailConf{
|
||||
// From: params.SmtpFrom,
|
||||
// Password: params.SmtpPassword,
|
||||
// SmtpHost: params.SmtpHost,
|
||||
// SmtpPort: params.SmtpPort,
|
||||
//}
|
||||
|
||||
address := fmt.Sprintf("%s:%d", params.Host, params.Port)
|
||||
lis, err := net.Listen("tcp", address)
|
||||
@@ -197,17 +156,12 @@ func server(ctx context.Context, params Serve) error {
|
||||
grpc.UnaryInterceptor(
|
||||
grpc_zap.UnaryServerInterceptor(logger.SetupLogger("info")),
|
||||
),
|
||||
grpc.UnaryInterceptor(AuthInterceptor),
|
||||
grpc.StreamInterceptor(grpc_zap.StreamServerInterceptor(logger.SetupLogger("info"))),
|
||||
)
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: params.RedisHost,
|
||||
})
|
||||
err = rdb.Set(ctx, "key", "value", 0).Err()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if params.Reflection {
|
||||
reflection.Register(grpcServer)
|
||||
}
|
||||
@@ -216,14 +170,12 @@ func server(ctx context.Context, params Serve) error {
|
||||
HashCost: params.HashCost,
|
||||
DB: db,
|
||||
DevMode: params.DevMode,
|
||||
RefrestTokenTTL: params.RefrestTokenTTL,
|
||||
RefreshTokenTTL: params.RefrestTokenTTL,
|
||||
AccessTokenTTL: params.AccessTokenTTL,
|
||||
JWTSecret: []byte(params.JWTSecret),
|
||||
Redis: rdb,
|
||||
}
|
||||
environments.RegisterEnvironmentsServer(grpcServer, v1.NewapiGrpcImpl(controller, log))
|
||||
accounts.RegisterAccountsNoAuthServiceServer(grpcServer, v1.NewAccountRPCImpl(accountCtrl))
|
||||
email_proto.RegisterEmailValidationServer(grpcServer, v1.InitEmailServer(controller, &emailConfig, params.DevMode))
|
||||
applications_proto.RegisterApplicationsServer(grpcServer, v1.NewApplicationsGrpcImpl(controller, log))
|
||||
|
||||
if err := grpcServer.Serve(lis); err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user