Skip to content

Commit

Permalink
add role-based access control (RBAC) (#103)
Browse files Browse the repository at this point in the history
* add role-based access control (RBAC)

* update lecture video links
  • Loading branch information
phamlequang authored Oct 10, 2023
1 parent 6e26798 commit 2e2094e
Show file tree
Hide file tree
Showing 32 changed files with 248 additions and 133 deletions.
142 changes: 72 additions & 70 deletions README.md

Large diffs are not rendered by default.

24 changes: 12 additions & 12 deletions api/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,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 @@ -52,7 +52,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 @@ -82,7 +82,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 @@ -99,7 +99,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 @@ -115,7 +115,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 @@ -169,7 +169,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 @@ -210,7 +210,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 @@ -228,7 +228,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 @@ -297,7 +297,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 @@ -340,7 +340,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 @@ -359,7 +359,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 @@ -377,7 +377,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 @@ -10,6 +10,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
"github.com/techschool/simplebank/token"
"github.com/techschool/simplebank/util"
)

func addAuthorization(
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
20 changes: 10 additions & 10 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
2 changes: 2 additions & 0 deletions api/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ func (server *Server) loginUser(ctx *gin.Context) {

accessToken, accessPayload, err := server.tokenMaker.CreateToken(
user.Username,
user.Role,
server.config.AccessTokenDuration,
)
if err != nil {
Expand All @@ -118,6 +119,7 @@ func (server *Server) loginUser(ctx *gin.Context) {

refreshToken, refreshPayload, err := server.tokenMaker.CreateToken(
user.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";
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';
2 changes: 1 addition & 1 deletion db/sqlc/account.sql.go

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

2 changes: 1 addition & 1 deletion db/sqlc/db.go

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

2 changes: 1 addition & 1 deletion db/sqlc/entry.sql.go

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

3 changes: 2 additions & 1 deletion db/sqlc/models.go

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

2 changes: 1 addition & 1 deletion db/sqlc/querier.go

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

2 changes: 1 addition & 1 deletion db/sqlc/session.sql.go

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

2 changes: 1 addition & 1 deletion db/sqlc/transfer.sql.go

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

Loading

0 comments on commit 2e2094e

Please sign in to comment.