Skip to content

Commit

Permalink
Merge pull request #22 from imrishuroy/ft/RBAC
Browse files Browse the repository at this point in the history
Implement role-based access control (RBAC)
  • Loading branch information
imrishuroy authored Dec 9, 2023
2 parents 66200cd + 1eedbc0 commit fd485f2
Show file tree
Hide file tree
Showing 27 changed files with 101 additions and 73 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ COPY start.sh .
COPY wait-for.sh .
COPY db/migration ./db/migration

EXPOSE 8080
EXPOSE 8080 9090
CMD [ "/app/main" ]
ENTRYPOINT [ "/app/start.sh" ]
18 changes: 0 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,3 @@ To run gRPC client
Test redis server

docker exec -it redis redis-cli ping

docker run --name simplebank --network bank-network -p 8080:8080 -e GIN_MODE=release -e DB_SOURCE="postgresql://root:Prince2024@postgres12:5432/simple_bank?sslmode=disable" simplebank:latest

To get external API
kubectl get services simple-bank-api-service

proto:
rm -f pb/*.go
Remove-Item pb/*.go -Force
protoc --proto_path=proto --go_out=pb --go_opt=paths=source_relative \
--go-grpc_out=pb --go-grpc_opt=paths=source_relative \
--openapiv2_out=doc/swagger \
proto/*.proto


https://medium.com/@rafaellevissa/microservices-architecture-in-golang-a-transformative-experience-596d1427a29a


24 changes: 12 additions & 12 deletions api/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func TestGetAccountAPI(t *testing.T) {
name: "OK",
accountID: account.ID,
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user.Username, time.Minute)
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user.Username, user.Role, time.Minute)
},
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().
Expand All @@ -53,7 +53,7 @@ func TestGetAccountAPI(t *testing.T) {
name: "UnauthorizedUser",
accountID: account.ID,
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, "unauthorized_user", time.Minute)
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, "unauthorized_user", util.DepositorRole, time.Minute)
},
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().
Expand Down Expand Up @@ -83,7 +83,7 @@ func TestGetAccountAPI(t *testing.T) {
name: "NotFound",
accountID: account.ID,
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user.Username, time.Minute)
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user.Username, user.Role, time.Minute)
},

buildStubs: func(store *mockdb.MockStore) {
Expand All @@ -100,7 +100,7 @@ func TestGetAccountAPI(t *testing.T) {
name: "InternalError",
accountID: account.ID,
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user.Username, time.Minute)
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user.Username, user.Role, time.Minute)
},
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().
Expand All @@ -116,7 +116,7 @@ func TestGetAccountAPI(t *testing.T) {
name: "InvalidID",
accountID: 0,
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user.Username, time.Minute)
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user.Username, user.Role, time.Minute)
},
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().
Expand Down Expand Up @@ -170,7 +170,7 @@ func TestCreateAccountAPI(t *testing.T) {
"currency": account.Currency,
},
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user.Username, time.Minute)
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user.Username, user.Role, time.Minute)
},
buildStubs: func(store *mockdb.MockStore) {
arg := db.CreateAccountParams{
Expand Down Expand Up @@ -211,7 +211,7 @@ func TestCreateAccountAPI(t *testing.T) {
"currency": account.Currency,
},
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user.Username, time.Minute)
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user.Username, user.Role, time.Minute)
},
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().
Expand All @@ -229,7 +229,7 @@ func TestCreateAccountAPI(t *testing.T) {
"currency": "invalid",
},
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user.Username, time.Minute)
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user.Username, user.Role, time.Minute)
},
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().
Expand Down Expand Up @@ -298,7 +298,7 @@ func TestListAccountsAPI(t *testing.T) {
pageSize: n,
},
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user.Username, time.Minute)
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user.Username, user.Role, time.Minute)
},
buildStubs: func(store *mockdb.MockStore) {
arg := db.ListAccountsParams{
Expand Down Expand Up @@ -341,7 +341,7 @@ func TestListAccountsAPI(t *testing.T) {
pageSize: n,
},
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user.Username, time.Minute)
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user.Username, user.Role, time.Minute)
},
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().
Expand All @@ -360,7 +360,7 @@ func TestListAccountsAPI(t *testing.T) {
pageSize: n,
},
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user.Username, time.Minute)
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user.Username, user.Role, time.Minute)
},
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().
Expand All @@ -378,7 +378,7 @@ func TestListAccountsAPI(t *testing.T) {
pageSize: 100000,
},
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user.Username, time.Minute)
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user.Username, user.Role, time.Minute)
},
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().
Expand Down
15 changes: 10 additions & 5 deletions api/middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/gin-gonic/gin"
"github.com/imrishuroy/simplebank/token"
"github.com/imrishuroy/simplebank/util"
"github.com/stretchr/testify/require"
)

Expand All @@ -18,9 +19,10 @@ func addAuthorization(
tokenMaker token.Maker,
authorizationType string,
username string,
role string,
duration time.Duration,
) {
token, payload, err := tokenMaker.CreateToken(username, duration)
token, payload, err := tokenMaker.CreateToken(username, role, duration)
require.NoError(t, err)
require.NotEmpty(t, payload)

Expand All @@ -29,6 +31,9 @@ func addAuthorization(
}

func TestAuthMiddleware(t *testing.T) {
username := util.RandomOwner()
role := util.DepositorRole

testCases := []struct {
name string
setupAuth func(t *testing.T, request *http.Request, tokenMaker token.Maker)
Expand All @@ -37,7 +42,7 @@ func TestAuthMiddleware(t *testing.T) {
{
name: "OK",
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, "user", time.Minute)
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, username, role, time.Minute)
},
checkResponse: func(t *testing.T, recorder *httptest.ResponseRecorder) {
require.Equal(t, http.StatusOK, recorder.Code)
Expand All @@ -54,7 +59,7 @@ func TestAuthMiddleware(t *testing.T) {
{
name: "UnsupportedAuthorization",
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, "unsupported", "user", time.Minute)
addAuthorization(t, request, tokenMaker, "unsupported", username, role, time.Minute)
},
checkResponse: func(t *testing.T, recorder *httptest.ResponseRecorder) {
require.Equal(t, http.StatusUnauthorized, recorder.Code)
Expand All @@ -63,7 +68,7 @@ func TestAuthMiddleware(t *testing.T) {
{
name: "InvalidAuthorizationFormat",
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, "", "user", time.Minute)
addAuthorization(t, request, tokenMaker, "", username, role, time.Minute)
},
checkResponse: func(t *testing.T, recorder *httptest.ResponseRecorder) {
require.Equal(t, http.StatusUnauthorized, recorder.Code)
Expand All @@ -72,7 +77,7 @@ func TestAuthMiddleware(t *testing.T) {
{
name: "ExpiredToken",
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, "user", -time.Minute)
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, username, role, -time.Minute)
},
checkResponse: func(t *testing.T, recorder *httptest.ResponseRecorder) {
require.Equal(t, http.StatusUnauthorized, recorder.Code)
Expand Down
1 change: 1 addition & 0 deletions api/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func (server *Server) renewAccessToken(ctx *gin.Context) {

accessToken, accessPayload, err := server.tokenMaker.CreateToken(
refreshPayload.Username,
refreshPayload.Role,
server.config.AccessTokenDuration,
)
if err != nil {
Expand Down
21 changes: 10 additions & 11 deletions api/transfer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func TestTransferAPI(t *testing.T) {
"currency": util.USD,
},
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user1.Username, time.Minute)
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user1.Username, user1.Role, time.Minute)
},
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().GetAccount(gomock.Any(), gomock.Eq(account1.ID)).Times(1).Return(account1, nil)
Expand All @@ -75,7 +75,7 @@ func TestTransferAPI(t *testing.T) {
"currency": util.USD,
},
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user2.Username, time.Minute)
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user2.Username, user2.Role, time.Minute)
},
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().GetAccount(gomock.Any(), gomock.Eq(account1.ID)).Times(1).Return(account1, nil)
Expand Down Expand Up @@ -113,7 +113,7 @@ func TestTransferAPI(t *testing.T) {
"currency": util.USD,
},
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user1.Username, time.Minute)
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user1.Username, user1.Role, time.Minute)
},
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().GetAccount(gomock.Any(), gomock.Eq(account1.ID)).Times(1).Return(db.Account{}, db.ErrRecordNotFound)
Expand All @@ -133,7 +133,7 @@ func TestTransferAPI(t *testing.T) {
"currency": util.USD,
},
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user1.Username, time.Minute)
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user1.Username, user1.Role, time.Minute)
},
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().GetAccount(gomock.Any(), gomock.Eq(account1.ID)).Times(1).Return(account1, nil)
Expand All @@ -153,7 +153,7 @@ func TestTransferAPI(t *testing.T) {
"currency": util.USD,
},
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user3.Username, time.Minute)
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user3.Username, user3.Role, time.Minute)
},
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().GetAccount(gomock.Any(), gomock.Eq(account3.ID)).Times(1).Return(account3, nil)
Expand All @@ -173,7 +173,7 @@ func TestTransferAPI(t *testing.T) {
"currency": util.USD,
},
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user1.Username, time.Minute)
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user1.Username, user1.Role, time.Minute)
},
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().GetAccount(gomock.Any(), gomock.Eq(account1.ID)).Times(1).Return(account1, nil)
Expand All @@ -193,7 +193,7 @@ func TestTransferAPI(t *testing.T) {
"currency": "XYZ",
},
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user1.Username, time.Minute)
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user1.Username, user1.Role, time.Minute)
},
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().GetAccount(gomock.Any(), gomock.Any()).Times(0)
Expand All @@ -212,7 +212,7 @@ func TestTransferAPI(t *testing.T) {
"currency": util.USD,
},
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user1.Username, time.Minute)
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user1.Username, user1.Role, time.Minute)
},
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().GetAccount(gomock.Any(), gomock.Any()).Times(0)
Expand All @@ -231,7 +231,7 @@ func TestTransferAPI(t *testing.T) {
"currency": util.USD,
},
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user1.Username, time.Minute)
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user1.Username, user1.Role, time.Minute)
},
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().GetAccount(gomock.Any(), gomock.Any()).Times(1).Return(db.Account{}, sql.ErrConnDone)
Expand All @@ -250,7 +250,7 @@ func TestTransferAPI(t *testing.T) {
"currency": util.USD,
},
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user1.Username, time.Minute)
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, user1.Username, user1.Role, time.Minute)
},
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().GetAccount(gomock.Any(), gomock.Eq(account1.ID)).Times(1).Return(account1, nil)
Expand Down Expand Up @@ -289,5 +289,4 @@ func TestTransferAPI(t *testing.T) {
tc.checkResponse(recorder)
})
}

}
3 changes: 2 additions & 1 deletion api/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ func (server *Server) loginUser(ctx *gin.Context) {

accessToken, accessPayload, err := server.tokenMaker.CreateToken(
user.Username,
user.Role,
server.config.AccessTokenDuration,
)

Expand All @@ -116,7 +117,7 @@ func (server *Server) loginUser(ctx *gin.Context) {
}

refreshToken, refreshPayload, err := server.tokenMaker.CreateToken(
req.Username, server.config.RefreshTokenDuration,
req.Username, user.Role, server.config.RefreshTokenDuration,
)

if err != nil {
Expand Down
1 change: 1 addition & 0 deletions db/migration/000005_add_role_to_users.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE "users" DROP COLUMN "role" varchar NOT NULL DEFAULT 'depositor';
1 change: 1 addition & 0 deletions db/migration/000005_add_role_to_users.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE "users" ADD COLUMN "role" varchar NOT NULL DEFAULT 'depositor';
1 change: 1 addition & 0 deletions db/sqlc/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions db/sqlc/user.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions doc/db.dbml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Project simple_bank {

Table users as U {
username varchar [pk]
role varchar [not null, default: 'depositor']
hashed_password varchar [not null]
full_name varchar [not null]
email varchar [unique, not null]
Expand Down
Loading

0 comments on commit fd485f2

Please # to comment.