From c81d9824cdec821d14bc7a3521410365ca57b345 Mon Sep 17 00:00:00 2001 From: "Gustavo \"Guz\" L de Mello" Date: Tue, 10 Jun 2025 19:06:01 -0300 Subject: [PATCH] feat(service,token): properly implement token.issue method --- service/token.go | 71 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 61 insertions(+), 10 deletions(-) diff --git a/service/token.go b/service/token.go index 2c9109c..1e56374 100644 --- a/service/token.go +++ b/service/token.go @@ -1,34 +1,85 @@ package service import ( + "crypto/ed25519" + "errors" + "log/slog" "time" "forge.capytal.company/capytalcode/project-comicverse/model" + "forge.capytal.company/capytalcode/project-comicverse/repository" "forge.capytal.company/loreddev/x/tinyssert" "github.com/golang-jwt/jwt/v4" "github.com/google/uuid" ) -type TokenService struct { +type Token struct { + privateKey ed25519.PrivateKey + publicKey ed25519.PublicKey + + repo *repository.Token + + log *slog.Logger assert tinyssert.Assertions } -func NewTokenService(assert tinyssert.Assertions) *TokenService { - return &TokenService{assert: assert} +func NewToken( + privateKey ed25519.PrivateKey, + publicKey ed25519.PublicKey, + repo *repository.Token, + logger *slog.Logger, + assert tinyssert.Assertions, +) *Token { + assert.NotZero(privateKey) + assert.NotZero(publicKey) + assert.NotZero(repo) + assert.NotZero(logger) + + return &Token{assert: assert} } -func (s *TokenService) Issue(user model.User) (*jwt.Token, error) { - id, err := uuid.NewV7() +func (svc *Token) Issue(user model.User) (string, error) { // TODO: Return a refresh token + svc.assert.NotNil(svc.privateKey) + svc.assert.NotNil(svc.log) + svc.assert.NotZero(user) + + log := svc.log.With(slog.String("user_id", user.ID.String())) + log.Info("Issuing new token") + defer log.Info("Finished issuing token") + + jti, err := uuid.NewV7() if err != nil { - return nil, err + return "", errors.Join(errors.New("service: failed to generate token UUID"), err) } now := time.Now() + expires := now.Add(30 * 24 * time.Hour) // TODO: Make the JWT short lived and use refresh tokens to create new JWTs - t := jwt.NewWithClaims(jwt.SigningMethodES256, jwt.RegisteredClaims{ - ID: id.String(), - Subject: user.Username, - IssuedAt: jwt.NewNumericDate(now), + t := jwt.NewWithClaims(jwt.SigningMethodEdDSA, jwt.RegisteredClaims{ + Issuer: "comicverse", // TODO: Make application ID and Name be a parameter + Subject: user.ID.String(), + Audience: jwt.ClaimStrings{"comicverse"}, // TODO: When we have third-party apps integration, this should be the name/URI/id of the app + ExpiresAt: jwt.NewNumericDate(expires), NotBefore: jwt.NewNumericDate(now), + IssuedAt: jwt.NewNumericDate(now), + ID: jti.String(), }) + + signed, err := t.SignedString(svc.privateKey) + if err != nil { + return "", errors.Join(errors.New("service: failed to sign token"), err) + } + + // TODO: Store refresh tokens in repo + err = svc.repo.Create(model.Token{ + ID: jti, + DateCreated: now, + DateExpires: expires, + }) + if err != nil { + return "", errors.Join(errors.New("service: failed to save token"), err) + } + + return signed, nil } +