【Go Web 開發】授予用戶權限

上一篇文章我們的權限模型和權限檢查中間件已經可以正常運行了。但此時,當新用戶註冊一個帳戶時,他們沒有任何權限。在本節中,我們將修改這個設置,使新用戶在默認情況下自動被授予 “movies:read” 權限。

更新權限模型

爲了給用戶授予權限,我們需要更新 PermissionModel,添加 AddForUser() 方法,爲用戶添加一個或多個權限碼到數據庫中。我們的想法是,按以下方式在處理程序中使用該函數:

//爲ID = 2的用戶添加"movies:read""movies:write"權限
app.models.Permissions.AddForUser(2, "movies:read", "movies:write")

該函數在數據庫中執行的 SQL 語句如下所示:

INSERT INTO users_permissions
SELECT $1, permissions.id FROM permissions WHERE permissions.code = ANY($2)

在這個 SQL 語句中 $1 參數是用戶 ID,$2 參數是我們需要爲用戶添加的權限碼列表,類似 {'movies:read', 'movies:write'}。

因此,這裏發生的事是第二行的 SELECT 語句創建了一個 “臨時” 表,其中的行由用戶 ID 和數組中相應權限代碼的 ID 組成。然後將這個臨時表的內容插入到 user_permissions 表中。

下面我們在 internal/data/permissions.go 文件中創建 AddForUser() 方法:

File: internal/data/permissions.go

package data

...

//爲特定用戶添加授權碼。這裏我們使用可變參數。
func (m PermissionModel)AddForUser(userID int64, codes ...string) error {
    query := `
        INSERT INTO users_permissions
        SELECT $1, permissions.id FROM permissions WHERE permissions.code = ANY($2)`

    ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
    defer cancel()

    _, err := m.DB.ExecContext(ctx, query, userID, pq.Array(codes))
    return err
}

更新註冊處理程序 (register handler)

現在數據庫處理完成了,我們更新 registerUserHandler 這樣在新用戶註冊的時自動爲用戶創建 "movies:read" 權限。如下所示:

File:cmd/api/users.go

package main

...

func (app *application) registerUserHandler(w http.ResponseWriter, r *http.Request) {
    //創建匿名結構體接收客戶端發送用戶信息
    var input struct {
        Name     string `json:"name"`
        Email    string `json:"email"`
        Password string `json:"password"`
    }
    //解析請求內容到匿名結構體只能夠
    err := app.readJSON(w, r, &input)
    if err != nil {
        app.badRequestResponse(w, r, err)
        return
    }
    //將input中到用戶信息拷貝到User結構體。注意需要將激活信息設置爲false,
    //該操作是非必需的因爲默認值就是false,單獨設置下可讀性更好。
    user := &data.User{
        Name:      input.Name,
        Email:     input.Email,
        Activated: false,
    }
    //使用Password.Set方法處理密碼
    err = user.Password.Set(input.Password)
    if err != nil {
        app.serverErrorResponse(w, r, err)
        return
    }
    v := validator.New()
    //校驗user結構體
    if data.ValidateUser(v, user); !v.Valid() {
        app.failedValidationResponse(w, r, v.Errors)
        return
    }
    //插入用戶信息到數據庫
    err = app.models.Users.Insert(user)
    if err != nil {
        switch {
        //如果錯誤是ErrDuplicateEmail,使用v.AddError()方法手動添加校驗錯誤信息
        case errors.Is(err, data.ErrDuplicateEmail):
            v.AddError("email", "a user with this email address already exists")
            app.failedValidationResponse(w, r, v.Errors)
        default:
            app.serverErrorResponse(w, r, err)
        }
        return
    }

    //爲新註冊用戶添加"movies:read"權限
    err = app.models.Permissions.AddForUser(user.ID, "movies:read")
    if err != nil {
        app.serverErrorResponse(w, r, err)
        return
    }

    //用戶數據插入表之後,爲用戶生成新的激活token
    token, err := app.models.Tokens.New(user.ID, 3 * 24 * time.Hour, data.ScopeActivation)
    if err != nil {
        app.serverErrorResponse(w, r, err)
        return
    }

    //使用background創建goroutine異步發送郵件
    app.background(func() {
        //現在要傳入多個數據到郵件模版,我們創建一個map
        data := map[string]interface{}{
            "activationToken": token.Plaintext,
            "userID": user.ID,
        }
        // 發送歡迎郵件,並傳入map作爲動態數據
        err = app.mailer.Send(user.Email, "/user_welcome.tmpl", data)
        if err != nil {
            app.logger.Error(err, nil)
        }
    })

    //將返回碼改爲202,表示客戶端請求被接受,但處理沒有完成。
    err = app.writeJSON(w, http.StatusAccepted, envelope{"user":user}, nil)
    if err != nil {
        app.serverErrorResponse(w, r, err)
    }
}

...

我們用 grace@example.com 郵箱新註冊一個用戶來測試下前面代碼是否正常。

$ BODY='{"name": "Grace Smith", "email": "grace@example.com", "password": "pa55word"}'
$ curl -d "$BODY" localhost:4000/v1/users
{
        "user": {
                "id": 4,
                "create_at": "2022-01-08T16:08:17+08:00",
                "name": "Grace Smith",
                "email": "grace@example.com",
                "activated": false
        }
}

如果你打開 psql,執行以下 SQL 查詢應該可以看到新註冊的用戶有 movies:read 權限。

greenlight=> select email, code from users
inner join users_permissions on users.id = users_permissions.user_id
inner join permissions on users_permissions.permission_id = permissions.id
where users.email = 'grace@example.com';
       email       |    code   
-------------------+-------------
 grace@example.com | movies:read
(1 row)
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/KYuaCvblWxtpLQlqoJIhYA