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
8 changed files with 125 additions and 90 deletions

View File

@@ -2,15 +2,9 @@ package v1
import (
"context"
"errors"
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers"
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"
)
@@ -24,64 +18,25 @@ func NewAccountServer(
}
}
//var _ accounts.AccountsServiceServer = (*AccountsServer)(nil)
type AccountsServer struct {
accounts.UnimplementedAccountsServiceServer
accountsCtrl *controllers.AccountController
authorizationCtrl *controllers.AuthController
}
func (srv *AccountsServer) RefreshToken(ctx context.Context, in *empty.Empty) (*empty.Empty, error) {
claims, err := controllers.ClaimsFromContext(ctx)
if err != nil {
return nil, status.Error(codes.Aborted, "Context is invalid")
}
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")
}
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
// IsEmailVerified implements [v1.AccountsServiceServer].
func (a *AccountsServer) IsEmailVerified(context.Context, *accounts.IsEmailVerifiedRequest) (*accounts.IsEmailVerifiedResponse, error) {
panic("unimplemented")
}
// RemoveSession implements [v1.AccountsServiceServer].
func (a *AccountsServer) RemoveSession(context.Context, *accounts.RemoveSessionRequest) (*emptypb.Empty, error) {
panic("unimplemented")
}
// TokenAuthorization implements [v1.AccountsServiceServer].
func (a *AccountsServer) TokenAuthorization(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
panic("unimplemented")
}

View File

@@ -5,12 +5,8 @@ import (
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers"
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"
)
func NewPublicAccountServer(
@@ -29,7 +25,7 @@ type PublicAccountService struct {
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())
if err != nil {
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 {
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
return &accounts.SignInResponse{
TokenPair: &accounts.TokenPair{
AccessToken: accessToken,
RefreshToken: refreshToken,
},
}, nil
}
// 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{
Password: in.GetPassword(),
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")
}
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
return &accounts.SignUpResponse{
TokenPair: &accounts.TokenPair{
AccessToken: accessToken,
RefreshToken: refreshToken,
},
}, 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"`
RedisHost string `env:"SOFTPLAYER_REDIS_HOST"`
// JWT parameters
RefrestTokenTTL time.Duration `default:"8h"`
RefreshTokenTTL time.Duration `default:"8h"`
AccessTokenTTL time.Duration `default:"15m"`
JWTSecret string `default:"qwertyu9"`
// Dev and logging
@@ -71,7 +71,7 @@ func (cmd *Server) Run(ctx context.Context) error {
authController := controllers.NewAuthController(
[]byte(cmd.JWTSecret),
cmd.AccessTokenTTL,
cmd.RefrestTokenTTL,
cmd.RefreshTokenTTL,
rdb,
)
@@ -106,7 +106,7 @@ func (cmd *Server) Run(ctx context.Context) error {
HashCost: cmd.HashCost,
DB: db,
DevMode: cmd.DevMode,
RefreshTokenTTL: cmd.RefrestTokenTTL,
RefreshTokenTTL: cmd.RefreshTokenTTL,
AccessTokenTTL: cmd.AccessTokenTTL,
JWTSecret: []byte(cmd.JWTSecret),
Redis: rdb,
@@ -114,11 +114,12 @@ func (cmd *Server) Run(ctx context.Context) error {
// Services that should be accessible for tokens should go here
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.RegisterPublicTestServiceServer(grpcServer, v1.NewPublicTestServer())
tokens.RegisterTokensServiceServer(grpcServer, v1.NewTokensServer(tokenCtrl, authController))
tokens.RegisterPublicTokensServiceServer(grpcServer, v1.NewPublicTokensServer(tokenCtrl, authController))
accounts.RegisterPublicAccountsServiceServer(grpcServer, v1.NewPublicAccountServer(accountCtrl, authController))
info := grpcServer.GetServiceInfo()
tokenCtrl.SetGRPCInfo(info)
@@ -144,6 +145,10 @@ func selectorRequireAuth(ctx context.Context, callMeta interceptors.CallMeta) bo
return false
}
if strings.HasPrefix(serviceName, "RefreshSession") {
return false
}
if strings.Contains(serviceName, "ServerReflection") {
return false
}

4
go.mod
View File

@@ -43,8 +43,8 @@ require (
)
require (
gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260517200845-22f1b32dfad9
github.com/golang/protobuf v1.5.4
gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260528090010-7bf4ddafe7f0
github.com/golang/protobuf v1.5.4 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.42.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/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
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-20260517200845-22f1b32dfad9/go.mod h1:EcQEZ3NN06b3UmKxiRnQnXDDjQ9kmJgoQQBAS+fpRQw=
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-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/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=

View File

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

View File

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