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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,9 @@ This course is designed with a lot of details, so that everyone, even with very
- Lecture #74: [Graceful shutdown gRPC/HTTP servers and async worker](https://www.youtube.com/watch?v=TdB2W8l4dHw&list=PLy_6D98if3ULEtXtNSY_2qN21VCKgoQAE)
- Lecture #75: [Go 1.22 fixed the most common for-loop trap](https://www.youtube.com/watch?v=rIHO9TqJtQQ&list=PLy_6D98if3ULEtXtNSY_2qN21VCKgoQAE)
- Lecture #76: [Setup CORS policy with Go and VueJS](https://www.youtube.com/watch?v=hOz4f4SdArc&list=PLy_6D98if3ULEtXtNSY_2qN21VCKgoQAE)
- Lecture #77: [Upgrade golang JWT package to v5](https://www.youtube.com/watch?v=iVk3jOF1Cv4&list=PLy_6D98if3ULEtXtNSY_2qN21VCKgoQAE)

## Frontend course videos (Vue.JS)
## Frontend crash course videos (Vue.JS)

- Lecture #1: [Build reactive web app with VueJS](https://www.youtube.com/watch?v=fRGgDBCWQJg&list=PLy_6D98if3UI3rsFRTHM1LMtVprYMp-GT)
- Lecture #2: [Introduction to Vue router and Vue component](https://www.youtube.com/watch?v=4rv484TofFA&list=PLy_6D98if3UI3rsFRTHM1LMtVprYMp-GT)
Expand Down
2 changes: 1 addition & 1 deletion api/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func authMiddleware(tokenMaker token.Maker) gin.HandlerFunc {
}

accessToken := fields[1]
payload, err := tokenMaker.VerifyToken(accessToken)
payload, err := tokenMaker.VerifyToken(accessToken, token.TokenTypeAccessToken)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err))
return
Expand Down
2 changes: 1 addition & 1 deletion api/middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func addAuthorization(
role string,
duration time.Duration,
) {
token, payload, err := tokenMaker.CreateToken(username, role, duration)
token, payload, err := tokenMaker.CreateToken(username, role, duration, token.TokenTypeAccessToken)
require.NoError(t, err)
require.NotEmpty(t, payload)

Expand Down
4 changes: 3 additions & 1 deletion api/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/gin-gonic/gin"
db "github.com/techschool/simplebank/db/sqlc"
"github.com/techschool/simplebank/token"
)

type renewAccessTokenRequest struct {
Expand All @@ -26,7 +27,7 @@ func (server *Server) renewAccessToken(ctx *gin.Context) {
return
}

refreshPayload, err := server.tokenMaker.VerifyToken(req.RefreshToken)
refreshPayload, err := server.tokenMaker.VerifyToken(req.RefreshToken, token.TokenTypeRefreshToken)
if err != nil {
ctx.JSON(http.StatusUnauthorized, errorResponse(err))
return
Expand Down Expand Up @@ -70,6 +71,7 @@ func (server *Server) renewAccessToken(ctx *gin.Context) {
refreshPayload.Username,
refreshPayload.Role,
server.config.AccessTokenDuration,
token.TokenTypeAccessToken,
)
if err != nil {
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
Expand Down
3 changes: 3 additions & 0 deletions api/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/google/uuid"
db "github.com/techschool/simplebank/db/sqlc"
"github.com/techschool/simplebank/token"
"github.com/techschool/simplebank/util"
)

Expand Down Expand Up @@ -111,6 +112,7 @@ func (server *Server) loginUser(ctx *gin.Context) {
user.Username,
user.Role,
server.config.AccessTokenDuration,
token.TokenTypeAccessToken,
)
if err != nil {
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
Expand All @@ -121,6 +123,7 @@ func (server *Server) loginUser(ctx *gin.Context) {
user.Username,
user.Role,
server.config.RefreshTokenDuration,
token.TokenTypeRefreshToken,
)
if err != nil {
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
Expand Down
2 changes: 1 addition & 1 deletion app.env
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ MIGRATION_URL=file://db/migration
HTTP_SERVER_ADDRESS=0.0.0.0:8080
GRPC_SERVER_ADDRESS=0.0.0.0:9090
TOKEN_SYMMETRIC_KEY=12345678901234567890123456789012
ACCESS_TOKEN_DURATION=15m
ACCESS_TOKEN_DURATION=1m
REFRESH_TOKEN_DURATION=24h
REDIS_ADDRESS=0.0.0.0:6379
EMAIL_SENDER_NAME=Simple Bank
Expand Down
2 changes: 1 addition & 1 deletion gapi/authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func (server *Server) authorizeUser(ctx context.Context, accessibleRoles []strin
}

accessToken := fields[1]
payload, err := server.tokenMaker.VerifyToken(accessToken)
payload, err := server.tokenMaker.VerifyToken(accessToken, token.TokenTypeAccessToken)
if err != nil {
return nil, fmt.Errorf("invalid access token: %s", err)
}
Expand Down
4 changes: 2 additions & 2 deletions gapi/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ func newTestServer(t *testing.T, store db.Store, taskDistributor worker.TaskDist
return server
}

func newContextWithBearerToken(t *testing.T, tokenMaker token.Maker, username string, role string, duration time.Duration) context.Context {
accessToken, _, err := tokenMaker.CreateToken(username, role, duration)
func newContextWithBearerToken(t *testing.T, tokenMaker token.Maker, username string, role string, duration time.Duration, tokenType token.TokenType) context.Context {
accessToken, _, err := tokenMaker.CreateToken(username, role, duration, tokenType)
require.NoError(t, err)

bearerToken := fmt.Sprintf("%s %s", authorizationBearer, accessToken)
Expand Down
2 changes: 1 addition & 1 deletion gapi/rpc_create_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func (server *Server) CreateUser(ctx context.Context, req *pb.CreateUserRequest)
txResult, err := server.store.CreateUserTx(ctx, arg)
if err != nil {
if db.ErrorCode(err) == db.UniqueViolation {
return nil, status.Errorf(codes.AlreadyExists, err.Error())
return nil, status.Error(codes.AlreadyExists, err.Error())
}
return nil, status.Errorf(codes.Internal, "failed to create user: %s", err)
}
Expand Down
3 changes: 3 additions & 0 deletions gapi/rpc_login_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

db "github.com/techschool/simplebank/db/sqlc"
"github.com/techschool/simplebank/pb"
"github.com/techschool/simplebank/token"
"github.com/techschool/simplebank/util"
"github.com/techschool/simplebank/val"
"google.golang.org/genproto/googleapis/rpc/errdetails"
Expand Down Expand Up @@ -37,6 +38,7 @@ func (server *Server) LoginUser(ctx context.Context, req *pb.LoginUserRequest) (
user.Username,
user.Role,
server.config.AccessTokenDuration,
token.TokenTypeAccessToken,
)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create access token")
Expand All @@ -46,6 +48,7 @@ func (server *Server) LoginUser(ctx context.Context, req *pb.LoginUserRequest) (
user.Username,
user.Role,
server.config.RefreshTokenDuration,
token.TokenTypeRefreshToken,
)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create refresh token")
Expand Down
34 changes: 28 additions & 6 deletions gapi/rpc_update_user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func TestUpdateUserAPI(t *testing.T) {
Return(updatedUser, nil)
},
buildContext: func(t *testing.T, tokenMaker token.Maker) context.Context {
return newContextWithBearerToken(t, tokenMaker, user.Username, user.Role, time.Minute)
return newContextWithBearerToken(t, tokenMaker, user.Username, user.Role, time.Minute, token.TokenTypeAccessToken)
},
checkResponse: func(t *testing.T, res *pb.UpdateUserResponse, err error) {
require.NoError(t, err)
Expand Down Expand Up @@ -112,7 +112,7 @@ func TestUpdateUserAPI(t *testing.T) {
Return(updatedUser, nil)
},
buildContext: func(t *testing.T, tokenMaker token.Maker) context.Context {
return newContextWithBearerToken(t, tokenMaker, banker.Username, banker.Role, time.Minute)
return newContextWithBearerToken(t, tokenMaker, banker.Username, banker.Role, time.Minute, token.TokenTypeAccessToken)
},
checkResponse: func(t *testing.T, res *pb.UpdateUserResponse, err error) {
require.NoError(t, err)
Expand All @@ -136,7 +136,7 @@ func TestUpdateUserAPI(t *testing.T) {
Times(0)
},
buildContext: func(t *testing.T, tokenMaker token.Maker) context.Context {
return newContextWithBearerToken(t, tokenMaker, other.Username, other.Role, time.Minute)
return newContextWithBearerToken(t, tokenMaker, other.Username, other.Role, time.Minute, token.TokenTypeAccessToken)
},
checkResponse: func(t *testing.T, res *pb.UpdateUserResponse, err error) {
require.Error(t, err)
Expand All @@ -159,7 +159,7 @@ func TestUpdateUserAPI(t *testing.T) {
Return(db.User{}, db.ErrRecordNotFound)
},
buildContext: func(t *testing.T, tokenMaker token.Maker) context.Context {
return newContextWithBearerToken(t, tokenMaker, user.Username, user.Role, time.Minute)
return newContextWithBearerToken(t, tokenMaker, user.Username, user.Role, time.Minute, token.TokenTypeAccessToken)
},
checkResponse: func(t *testing.T, res *pb.UpdateUserResponse, err error) {
require.Error(t, err)
Expand All @@ -181,7 +181,7 @@ func TestUpdateUserAPI(t *testing.T) {
Times(0)
},
buildContext: func(t *testing.T, tokenMaker token.Maker) context.Context {
return newContextWithBearerToken(t, tokenMaker, user.Username, user.Role, time.Minute)
return newContextWithBearerToken(t, tokenMaker, user.Username, user.Role, time.Minute, token.TokenTypeAccessToken)
},
checkResponse: func(t *testing.T, res *pb.UpdateUserResponse, err error) {
require.Error(t, err)
Expand All @@ -203,7 +203,29 @@ func TestUpdateUserAPI(t *testing.T) {
Times(0)
},
buildContext: func(t *testing.T, tokenMaker token.Maker) context.Context {
return newContextWithBearerToken(t, tokenMaker, user.Username, user.Role, -time.Minute)
return newContextWithBearerToken(t, tokenMaker, user.Username, user.Role, -time.Minute, token.TokenTypeAccessToken)
},
checkResponse: func(t *testing.T, res *pb.UpdateUserResponse, err error) {
require.Error(t, err)
st, ok := status.FromError(err)
require.True(t, ok)
require.Equal(t, codes.Unauthenticated, st.Code())
},
},
{
name: "WrongTokenType",
req: &pb.UpdateUserRequest{
Username: user.Username,
FullName: &newName,
Email: &newEmail,
},
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().
UpdateUser(gomock.Any(), gomock.Any()).
Times(0)
},
buildContext: func(t *testing.T, tokenMaker token.Maker) context.Context {
return newContextWithBearerToken(t, tokenMaker, user.Username, user.Role, time.Minute, token.TokenTypeRefreshToken)
},
checkResponse: func(t *testing.T, res *pb.UpdateUserResponse, err error) {
require.Error(t, err)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/techschool/simplebank

go 1.22
go 1.24

require (
github.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29
Expand Down
Binary file added simplebank
Binary file not shown.
11 changes: 8 additions & 3 deletions token/jwt_maker.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ func NewJWTMaker(secretKey string) (Maker, error) {
}

// CreateToken creates a new token for a specific username and duration
func (maker *JWTMaker) CreateToken(username string, role string, duration time.Duration) (string, *Payload, error) {
payload, err := NewPayload(username, role, duration)
func (maker *JWTMaker) CreateToken(username string, role string, duration time.Duration, tokenType TokenType) (string, *Payload, error) {
payload, err := NewPayload(username, role, duration, tokenType)
if err != nil {
return "", payload, err
}
Expand All @@ -36,7 +36,7 @@ func (maker *JWTMaker) CreateToken(username string, role string, duration time.D
}

// VerifyToken checks if the token is valid or not
func (maker *JWTMaker) VerifyToken(token string) (*Payload, error) {
func (maker *JWTMaker) VerifyToken(token string, tokenType TokenType) (*Payload, error) {
keyFunc := func(token *jwt.Token) (interface{}, error) {
_, ok := token.Method.(*jwt.SigningMethodHMAC)
if !ok {
Expand All @@ -58,5 +58,10 @@ func (maker *JWTMaker) VerifyToken(token string) (*Payload, error) {
return nil, ErrInvalidToken
}

err = payload.Valid(tokenType)
if err != nil {
return nil, err
}

return payload, nil
}
27 changes: 21 additions & 6 deletions token/jwt_maker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ func TestJWTMaker(t *testing.T) {
issuedAt := time.Now()
expiredAt := issuedAt.Add(duration)

token, payload, err := maker.CreateToken(username, role, duration)
token, payload, err := maker.CreateToken(username, role, duration, TokenTypeAccessToken)
require.NoError(t, err)
require.NotEmpty(t, token)
require.NotEmpty(t, payload)

payload, err = maker.VerifyToken(token)
payload, err = maker.VerifyToken(token, TokenTypeAccessToken)
require.NoError(t, err)
require.NotEmpty(t, token)

Expand All @@ -40,19 +40,19 @@ func TestExpiredJWTToken(t *testing.T) {
maker, err := NewJWTMaker(util.RandomString(32))
require.NoError(t, err)

token, payload, err := maker.CreateToken(util.RandomOwner(), util.DepositorRole, -time.Minute)
token, payload, err := maker.CreateToken(util.RandomOwner(), util.DepositorRole, -time.Minute, TokenTypeAccessToken)
require.NoError(t, err)
require.NotEmpty(t, token)
require.NotEmpty(t, payload)

payload, err = maker.VerifyToken(token)
payload, err = maker.VerifyToken(token, TokenTypeAccessToken)
require.Error(t, err)
require.EqualError(t, err, ErrExpiredToken.Error())
require.Nil(t, payload)
}

func TestInvalidJWTTokenAlgNone(t *testing.T) {
payload, err := NewPayload(util.RandomOwner(), util.DepositorRole, time.Minute)
payload, err := NewPayload(util.RandomOwner(), util.DepositorRole, time.Minute, TokenTypeAccessToken)
require.NoError(t, err)

jwtToken := jwt.NewWithClaims(jwt.SigningMethodNone, payload)
Expand All @@ -62,7 +62,22 @@ func TestInvalidJWTTokenAlgNone(t *testing.T) {
maker, err := NewJWTMaker(util.RandomString(32))
require.NoError(t, err)

payload, err = maker.VerifyToken(token)
payload, err = maker.VerifyToken(token, TokenTypeAccessToken)
require.Error(t, err)
require.EqualError(t, err, ErrInvalidToken.Error())
require.Nil(t, payload)
}

func TestJWTWrongTokenType(t *testing.T) {
maker, err := NewJWTMaker(util.RandomString(32))
require.NoError(t, err)

token, payload, err := maker.CreateToken(util.RandomOwner(), util.DepositorRole, time.Minute, TokenTypeAccessToken)
require.NoError(t, err)
require.NotEmpty(t, token)
require.NotEmpty(t, payload)

payload, err = maker.VerifyToken(token, TokenTypeRefreshToken)
require.Error(t, err)
require.EqualError(t, err, ErrInvalidToken.Error())
require.Nil(t, payload)
Expand Down
4 changes: 2 additions & 2 deletions token/maker.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
// Maker is an interface for managing tokens
type Maker interface {
// CreateToken creates a new token for a specific username and duration
CreateToken(username string, role string, duration time.Duration) (string, *Payload, error)
CreateToken(username string, role string, duration time.Duration, tokenType TokenType) (string, *Payload, error)

// VerifyToken checks if the token is valid or not
VerifyToken(token string) (*Payload, error)
VerifyToken(token string, tokenType TokenType) (*Payload, error)
}
8 changes: 4 additions & 4 deletions token/paseto_maker.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ func NewPasetoMaker(symmetricKey string) (Maker, error) {
}

// CreateToken creates a new token for a specific username and duration
func (maker *PasetoMaker) CreateToken(username string, role string, duration time.Duration) (string, *Payload, error) {
payload, err := NewPayload(username, role, duration)
func (maker *PasetoMaker) CreateToken(username string, role string, duration time.Duration, tokenType TokenType) (string, *Payload, error) {
payload, err := NewPayload(username, role, duration, tokenType)
if err != nil {
return "", payload, err
}
Expand All @@ -40,15 +40,15 @@ func (maker *PasetoMaker) CreateToken(username string, role string, duration tim
}

// VerifyToken checks if the token is valid or not
func (maker *PasetoMaker) VerifyToken(token string) (*Payload, error) {
func (maker *PasetoMaker) VerifyToken(token string, tokenType TokenType) (*Payload, error) {
payload := &Payload{}

err := maker.paseto.Decrypt(token, maker.symmetricKey, payload, nil)
if err != nil {
return nil, ErrInvalidToken
}

err = payload.Valid()
err = payload.Valid(tokenType)
if err != nil {
return nil, err
}
Expand Down
Loading