3 Commits

Author SHA1 Message Date
34d44cf1de Start writing logout method
All checks were successful
ci/woodpecker/push/build Pipeline was successful
Signed-off-by: Nikolai Rodionov <iam@allanger.xyz>
2026-05-29 11:21:59 +02:00
08087d453f Return tokens in the message body
All checks were successful
ci/woodpecker/push/build Pipeline was successful
Signed-off-by: Nikolai Rodionov <iam@allanger.xyz>
2026-05-27 13:54:32 +02:00
a9784c3436 Move refresh session to a separate service
All checks were successful
ci/woodpecker/push/build Pipeline was successful
Signed-off-by: Nikolai Rodionov <iam@allanger.xyz>
2026-05-27 13:32:21 +02:00
9 changed files with 126 additions and 91 deletions

View File

@@ -2,15 +2,9 @@ package v1
import ( import (
"context" "context"
"errors"
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers" "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers"
accounts "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/accounts/v1" accounts "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/accounts/v1"
"github.com/golang/protobuf/ptypes/empty"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/emptypb"
) )
@@ -24,64 +18,25 @@ func NewAccountServer(
} }
} }
//var _ accounts.AccountsServiceServer = (*AccountsServer)(nil)
type AccountsServer struct { type AccountsServer struct {
accounts.UnimplementedAccountsServiceServer accounts.UnimplementedAccountsServiceServer
accountsCtrl *controllers.AccountController accountsCtrl *controllers.AccountController
authorizationCtrl *controllers.AuthController authorizationCtrl *controllers.AuthController
} }
func (srv *AccountsServer) RefreshToken(ctx context.Context, in *empty.Empty) (*empty.Empty, error) { // IsEmailVerified implements [v1.AccountsServiceServer].
claims, err := controllers.ClaimsFromContext(ctx) func (a *AccountsServer) IsEmailVerified(context.Context, *accounts.IsEmailVerifiedRequest) (*accounts.IsEmailVerifiedResponse, error) {
if err != nil { panic("unimplemented")
return nil, status.Error(codes.Aborted, "Context is invalid") }
}
// RemoveSession implements [v1.AccountsServiceServer].
if claims.TokenType != controllers.TokenTypeRefresh { func (a *AccountsServer) RemoveSession(context.Context, *accounts.RemoveSessionRequest) (*emptypb.Empty, error) {
return nil, status.Error(codes.Unauthenticated, "Invalid token") panic("unimplemented")
} }
session, err := srv.authorizationCtrl.GetSession(ctx, claims.TokenID) // TokenAuthorization implements [v1.AccountsServiceServer].
if err != nil { func (a *AccountsServer) TokenAuthorization(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
if errors.Is(err, controllers.ErrSessionNotFound) { panic("unimplemented")
return nil, status.Error(codes.Unauthenticated, "Session doesn't exists")
}
return nil, status.Error(codes.Internal, "Somethings is broken on our side")
}
if session.UserID != claims.UserID {
return nil, status.Error(codes.Unauthenticated, "Invalid session")
}
accessToken, _, err := srv.authorizationCtrl.GenerateToken(&controllers.JWTData{
UserID: claims.UserID,
TokenType: controllers.TokenTypeAccess,
TokenAud: controllers.TokenAudWeb,
})
if err != nil {
return nil, status.Error(codes.Aborted, "Couldn't generate an access token")
}
refreshToken, tokenID, err := srv.authorizationCtrl.GenerateToken(&controllers.JWTData{
UserID: claims.UserID,
TokenType: controllers.TokenTypeRefresh,
TokenAud: controllers.TokenAudWeb,
})
if err != nil {
return nil, status.Error(codes.Aborted, "Couldn't generate an access token")
}
newSession := &controllers.Session{UserID: session.UserID}
if err := srv.authorizationCtrl.SaveSession(ctx, tokenID, newSession); err != nil {
return nil, status.Error(codes.Aborted, "Couldn't store session")
}
header := metadata.New(map[string]string{
"X-Access-Token": accessToken,
"X-Refresh-Token": refreshToken,
})
if err := grpc.SetHeader(ctx, header); err != nil {
return nil, status.Error(codes.Aborted, "Couldn't set metadata")
}
return &emptypb.Empty{}, nil
} }

View File

@@ -5,12 +5,8 @@ import (
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers" "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers"
accounts "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/accounts/v1" accounts "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/accounts/v1"
"github.com/golang/protobuf/ptypes/empty"
"google.golang.org/grpc"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
) )
func NewPublicAccountServer( func NewPublicAccountServer(
@@ -29,7 +25,7 @@ type PublicAccountService struct {
authorizationCtrl *controllers.AuthController authorizationCtrl *controllers.AuthController
} }
func (a *PublicAccountService) SignIn(ctx context.Context, in *accounts.SignInRequest) (*empty.Empty, error) { func (a *PublicAccountService) SignIn(ctx context.Context, in *accounts.SignInRequest) (*accounts.SignInResponse, error) {
id, err := a.accountsCtrl.Login(ctx, in.GetEmail(), in.GetPassword()) id, err := a.accountsCtrl.Login(ctx, in.GetEmail(), in.GetPassword())
if err != nil { if err != nil {
return nil, status.Error(codes.Aborted, "Couldn't create a user") return nil, status.Error(codes.Aborted, "Couldn't create a user")
@@ -57,18 +53,16 @@ func (a *PublicAccountService) SignIn(ctx context.Context, in *accounts.SignInRe
if err := a.authorizationCtrl.SaveSession(ctx, tokenID, session); err != nil { if err := a.authorizationCtrl.SaveSession(ctx, tokenID, session); err != nil {
return nil, status.Error(codes.Aborted, "Couldn't store session") return nil, status.Error(codes.Aborted, "Couldn't store session")
} }
header := metadata.New(map[string]string{ return &accounts.SignInResponse{
"X-Access-Token": accessToken, TokenPair: &accounts.TokenPair{
"X-Refresh-Token": refreshToken, AccessToken: accessToken,
}) RefreshToken: refreshToken,
if err := grpc.SetHeader(ctx, header); err != nil { },
return nil, status.Error(codes.Aborted, "Couldn't set metadata") }, nil
}
return &emptypb.Empty{}, nil
} }
// Create a new account in Softplayer // Create a new account in Softplayer
func (a *PublicAccountService) SignUp(ctx context.Context, in *accounts.SignUpRequest) (*empty.Empty, error) { func (a *PublicAccountService) SignUp(ctx context.Context, in *accounts.SignUpRequest) (*accounts.SignUpResponse, error) {
data := &controllers.AccountData{ data := &controllers.AccountData{
Password: in.GetPassword(), Password: in.GetPassword(),
Email: in.GetEmail(), Email: in.GetEmail(),
@@ -102,12 +96,10 @@ func (a *PublicAccountService) SignUp(ctx context.Context, in *accounts.SignUpRe
return nil, status.Error(codes.Aborted, "Couldn't store session") return nil, status.Error(codes.Aborted, "Couldn't store session")
} }
header := metadata.New(map[string]string{ return &accounts.SignUpResponse{
"X-Access-Token": accessToken, TokenPair: &accounts.TokenPair{
"X-Refresh-Token": refreshToken, AccessToken: accessToken,
}) RefreshToken: refreshToken,
if err := grpc.SetHeader(ctx, header); err != nil { },
return nil, status.Error(codes.Aborted, "Couldn't set metadata") }, nil
}
return &emptypb.Empty{}, nil
} }

78
api/v1/refresh_session.go Normal file
View File

@@ -0,0 +1,78 @@
package v1
import (
"context"
"errors"
"fmt"
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers"
accounts "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/accounts/v1"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func NewRefreshSessionServer(
authorizationCtrl *controllers.AuthController,
) *RefreshSessionService {
return &RefreshSessionService{
authorizationCtrl: authorizationCtrl,
}
}
type RefreshSessionService struct {
accounts.UnimplementedRefreshSessionServiceServer
authorizationCtrl *controllers.AuthController
}
func (srv *RefreshSessionService) RefreshSession(ctx context.Context, in *accounts.RefreshSessionRequest) (*accounts.RefreshSessionResponse, error) {
fmt.Println(in.GetRefreshToken())
claims, err := srv.authorizationCtrl.ParseToken(in.GetRefreshToken())
if err != nil {
fmt.Println(err)
return nil, status.Error(codes.Aborted, "Invalid token is sent")
}
if claims.TokenType != controllers.TokenTypeRefresh {
return nil, status.Error(codes.Unauthenticated, "Invalid token")
}
session, err := srv.authorizationCtrl.GetSession(ctx, claims.TokenID)
if err != nil {
if errors.Is(err, controllers.ErrSessionNotFound) {
return nil, status.Error(codes.Unauthenticated, "Session doesn't exists")
}
return nil, status.Error(codes.Internal, "Somethings is broken on our side")
}
if session.UserID != claims.UserID {
return nil, status.Error(codes.Unauthenticated, "Invalid session")
}
accessToken, _, err := srv.authorizationCtrl.GenerateToken(&controllers.JWTData{
UserID: claims.UserID,
TokenType: controllers.TokenTypeAccess,
TokenAud: controllers.TokenAudWeb,
})
if err != nil {
return nil, status.Error(codes.Aborted, "Couldn't generate an access token")
}
refreshToken, tokenID, err := srv.authorizationCtrl.GenerateToken(&controllers.JWTData{
UserID: claims.UserID,
TokenType: controllers.TokenTypeRefresh,
TokenAud: controllers.TokenAudWeb,
})
if err != nil {
return nil, status.Error(codes.Aborted, "Couldn't generate an access token")
}
newSession := &controllers.Session{UserID: session.UserID}
if err := srv.authorizationCtrl.SaveSession(ctx, tokenID, newSession); err != nil {
return nil, status.Error(codes.Aborted, "Couldn't store session")
}
return &accounts.RefreshSessionResponse{TokenPair: &accounts.TokenPair{
AccessToken: accessToken,
RefreshToken: refreshToken,
}}, nil
}

View File

@@ -38,7 +38,7 @@ type Server struct {
DBConnectionString string `env:"SOFTPLAYER_DB_CONNECTION_STRING"` DBConnectionString string `env:"SOFTPLAYER_DB_CONNECTION_STRING"`
RedisHost string `env:"SOFTPLAYER_REDIS_HOST"` RedisHost string `env:"SOFTPLAYER_REDIS_HOST"`
// JWT parameters // JWT parameters
RefrestTokenTTL time.Duration `default:"8h"` RefreshTokenTTL time.Duration `default:"8h"`
AccessTokenTTL time.Duration `default:"15m"` AccessTokenTTL time.Duration `default:"15m"`
JWTSecret string `default:"qwertyu9"` JWTSecret string `default:"qwertyu9"`
// Dev and logging // Dev and logging
@@ -71,7 +71,7 @@ func (cmd *Server) Run(ctx context.Context) error {
authController := controllers.NewAuthController( authController := controllers.NewAuthController(
[]byte(cmd.JWTSecret), []byte(cmd.JWTSecret),
cmd.AccessTokenTTL, cmd.AccessTokenTTL,
cmd.RefrestTokenTTL, cmd.RefreshTokenTTL,
rdb, rdb,
) )
@@ -106,7 +106,7 @@ func (cmd *Server) Run(ctx context.Context) error {
HashCost: cmd.HashCost, HashCost: cmd.HashCost,
DB: db, DB: db,
DevMode: cmd.DevMode, DevMode: cmd.DevMode,
RefreshTokenTTL: cmd.RefrestTokenTTL, RefreshTokenTTL: cmd.RefreshTokenTTL,
AccessTokenTTL: cmd.AccessTokenTTL, AccessTokenTTL: cmd.AccessTokenTTL,
JWTSecret: []byte(cmd.JWTSecret), JWTSecret: []byte(cmd.JWTSecret),
Redis: rdb, Redis: rdb,
@@ -114,11 +114,12 @@ func (cmd *Server) Run(ctx context.Context) error {
// Services that should be accessible for tokens should go here // Services that should be accessible for tokens should go here
accounts.RegisterAccountsServiceServer(grpcServer, v1.NewAccountServer(accountCtrl, authController)) accounts.RegisterAccountsServiceServer(grpcServer, v1.NewAccountServer(accountCtrl, authController))
accounts.RegisterPublicAccountsServiceServer(grpcServer, v1.NewPublicAccountServer(accountCtrl, authController))
accounts.RegisterRefreshSessionServiceServer(grpcServer, v1.NewRefreshSessionServer(authController))
test.RegisterTestServiceServer(grpcServer, v1.NewTestServer()) test.RegisterTestServiceServer(grpcServer, v1.NewTestServer())
test.RegisterPublicTestServiceServer(grpcServer, v1.NewPublicTestServer()) test.RegisterPublicTestServiceServer(grpcServer, v1.NewPublicTestServer())
tokens.RegisterTokensServiceServer(grpcServer, v1.NewTokensServer(tokenCtrl, authController)) tokens.RegisterTokensServiceServer(grpcServer, v1.NewTokensServer(tokenCtrl, authController))
tokens.RegisterPublicTokensServiceServer(grpcServer, v1.NewPublicTokensServer(tokenCtrl, authController)) tokens.RegisterPublicTokensServiceServer(grpcServer, v1.NewPublicTokensServer(tokenCtrl, authController))
accounts.RegisterPublicAccountsServiceServer(grpcServer, v1.NewPublicAccountServer(accountCtrl, authController))
info := grpcServer.GetServiceInfo() info := grpcServer.GetServiceInfo()
tokenCtrl.SetGRPCInfo(info) tokenCtrl.SetGRPCInfo(info)
@@ -144,6 +145,10 @@ func selectorRequireAuth(ctx context.Context, callMeta interceptors.CallMeta) bo
return false return false
} }
if strings.HasPrefix(serviceName, "RefreshSession") {
return false
}
if strings.Contains(serviceName, "ServerReflection") { if strings.Contains(serviceName, "ServerReflection") {
return false return false
} }

4
go.mod
View File

@@ -43,8 +43,8 @@ require (
) )
require ( require (
gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260517200845-22f1b32dfad9 gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260528090010-7bf4ddafe7f0
github.com/golang/protobuf v1.5.4 github.com/golang/protobuf v1.5.4 // indirect
golang.org/x/net v0.51.0 // indirect golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.42.0 // indirect golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.34.0 // indirect golang.org/x/text v0.34.0 // indirect

4
go.sum
View File

@@ -2,8 +2,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c= cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260517200845-22f1b32dfad9 h1:RP73i+SOZYmc61F+gZjO/rvUlpPP0Za4MLJKAgS+1YI= gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260528090010-7bf4ddafe7f0 h1:CI6EwQndn8cr6ofpc1HbDsphCwK3NOZrdl2PS0BnX+Q=
gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260517200845-22f1b32dfad9/go.mod h1:EcQEZ3NN06b3UmKxiRnQnXDDjQ9kmJgoQQBAS+fpRQw= gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260528090010-7bf4ddafe7f0/go.mod h1:EcQEZ3NN06b3UmKxiRnQnXDDjQ9kmJgoQQBAS+fpRQw=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=

View File

@@ -72,6 +72,9 @@ spec:
- server - server
- --dev-mode - --dev-mode
- --reflection - --reflection
{{- with .Values.args }}
{{ . | toYaml | nindent 12 }}
{{- end }}
ports: ports:
- name: grpc - name: grpc
containerPort: {{ .Values.service.port }} containerPort: {{ .Values.service.port }}

View File

@@ -15,6 +15,8 @@ podAnnotations: {}
podLabels: {} podLabels: {}
podSecurityContext: {} podSecurityContext: {}
securityContext: {} securityContext: {}
args: []
# capabilities: # capabilities:
# drop: # drop:
# - ALL # - ALL

View File

@@ -22,7 +22,7 @@ releases:
- name: postgres-instance - name: postgres-instance
namespace: softplayer namespace: softplayer
chart: cloudpirates/postgres chart: cloudpirates/postgres
version: 0.19.5 version: 0.19.4
installed: true installed: true
values: values:
- auth: - auth: