Feature: API Tokens (#848)
* initial commit * fix last_used timestamp --------- Co-authored-by: Christian Beutel <>
This commit is contained in:
58
db/main.go
58
db/main.go
@@ -136,6 +136,8 @@ func setupEventHandlers(app *pocketbase.PocketBase, client meilisearch.ServiceMa
|
||||
|
||||
app.OnRecordsListRequest("feed", "profile_feed").BindFunc(listFeedHandler())
|
||||
|
||||
app.OnRecordCreate("api_tokens").BindFunc(createAPITokenHandler())
|
||||
|
||||
app.OnRecordCreateRequest().BindFunc(sanitizeHTML())
|
||||
app.OnRecordUpdateRequest().BindFunc(sanitizeHTML())
|
||||
|
||||
@@ -955,6 +957,22 @@ func listFeedHandler() func(e *core.RecordsListRequestEvent) error {
|
||||
}
|
||||
}
|
||||
|
||||
func createAPITokenHandler() func(e *core.RecordEvent) error {
|
||||
return func(e *core.RecordEvent) error {
|
||||
rawToken := "wanderer_key_" + security.RandomString(32)
|
||||
|
||||
hashedKey := security.SHA256(rawToken)
|
||||
|
||||
e.Record.Set("token", hashedKey)
|
||||
|
||||
// Temporarily store rawToken so we can display it once to the user
|
||||
e.Record.WithCustomData(true)
|
||||
e.Record.Set("rawToken", rawToken)
|
||||
|
||||
return e.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func onBeforeServeHandler(client meilisearch.ServiceManager) func(se *core.ServeEvent) error {
|
||||
return func(se *core.ServeEvent) error {
|
||||
registerRoutes(se, client)
|
||||
@@ -1009,6 +1027,46 @@ func registerRoutes(se *core.ServeEvent, client meilisearch.ServiceManager) {
|
||||
return e.JSON(http.StatusOK, map[string]string{"status": "ok"})
|
||||
})
|
||||
|
||||
se.Router.POST("/auth/token", func(e *core.RequestEvent) error {
|
||||
var data struct {
|
||||
APIToken string `json:"api_token"`
|
||||
}
|
||||
if err := e.BindBody(&data); err != nil {
|
||||
return apis.NewBadRequestError("Failed to read request data", err)
|
||||
}
|
||||
|
||||
hashedAPIToken := security.SHA256(data.APIToken)
|
||||
|
||||
tokenRecord, err := e.App.FindFirstRecordByFilter(
|
||||
"api_tokens",
|
||||
"token = {:hash}",
|
||||
map[string]any{"hash": hashedAPIToken},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return apis.NewNotFoundError("Invalid or revoked API token", nil)
|
||||
}
|
||||
if !tokenRecord.GetDateTime("expiration").IsZero() &&
|
||||
tokenRecord.GetDateTime("expiration").Time().Before(time.Now()) {
|
||||
return apis.NewBadRequestError("Key has expired", nil)
|
||||
}
|
||||
|
||||
tokenRecord.Set("last_used", time.Now())
|
||||
if err := e.App.Save(tokenRecord); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userRecord, _ := e.App.FindRecordById("users", tokenRecord.GetString("user"))
|
||||
token, err := userRecord.NewAuthToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return e.JSON(http.StatusOK, map[string]any{
|
||||
"token": token,
|
||||
"record": userRecord,
|
||||
})
|
||||
})
|
||||
|
||||
se.Router.GET("/search/token", func(e *core.RequestEvent) error {
|
||||
searchRules := map[string]interface{}{
|
||||
"lists": map[string]string{"filter": "public = true"},
|
||||
|
||||
138
db/migrations/1772293922_created_api_tokens.go
Normal file
138
db/migrations/1772293922_created_api_tokens.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
m "github.com/pocketbase/pocketbase/migrations"
|
||||
)
|
||||
|
||||
func init() {
|
||||
m.Register(func(app core.App) error {
|
||||
jsonData := `{
|
||||
"createRule": "user = @request.auth.id",
|
||||
"deleteRule": "user = @request.auth.id",
|
||||
"fields": [
|
||||
{
|
||||
"autogeneratePattern": "[a-z0-9]{15}",
|
||||
"hidden": false,
|
||||
"id": "text3208210256",
|
||||
"max": 15,
|
||||
"min": 15,
|
||||
"name": "id",
|
||||
"pattern": "^[a-z0-9]+$",
|
||||
"presentable": false,
|
||||
"primaryKey": true,
|
||||
"required": true,
|
||||
"system": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"hidden": false,
|
||||
"id": "text1579384326",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "name",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"hidden": true,
|
||||
"id": "text1597481275",
|
||||
"max": 64,
|
||||
"min": 64,
|
||||
"name": "token",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"hidden": false,
|
||||
"id": "date617435213",
|
||||
"max": "",
|
||||
"min": "",
|
||||
"name": "expiration",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "date"
|
||||
},
|
||||
{
|
||||
"hidden": false,
|
||||
"id": "date4016875332",
|
||||
"max": "",
|
||||
"min": "",
|
||||
"name": "last_used",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "date"
|
||||
},
|
||||
{
|
||||
"cascadeDelete": false,
|
||||
"collectionId": "_pb_users_auth_",
|
||||
"hidden": false,
|
||||
"id": "relation2375276105",
|
||||
"maxSelect": 1,
|
||||
"minSelect": 0,
|
||||
"name": "user",
|
||||
"presentable": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "relation"
|
||||
},
|
||||
{
|
||||
"hidden": false,
|
||||
"id": "autodate2990389176",
|
||||
"name": "created",
|
||||
"onCreate": true,
|
||||
"onUpdate": false,
|
||||
"presentable": false,
|
||||
"system": false,
|
||||
"type": "autodate"
|
||||
},
|
||||
{
|
||||
"hidden": false,
|
||||
"id": "autodate3332085495",
|
||||
"name": "updated",
|
||||
"onCreate": true,
|
||||
"onUpdate": true,
|
||||
"presentable": false,
|
||||
"system": false,
|
||||
"type": "autodate"
|
||||
}
|
||||
],
|
||||
"id": "pbc_3525142174",
|
||||
"indexes": [],
|
||||
"listRule": "user = @request.auth.id",
|
||||
"name": "api_tokens",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": null,
|
||||
"viewRule": "user = @request.auth.id"
|
||||
}`
|
||||
|
||||
collection := &core.Collection{}
|
||||
if err := json.Unmarshal([]byte(jsonData), &collection); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return app.Save(collection)
|
||||
}, func(app core.App) error {
|
||||
collection, err := app.FindCollectionByNameOrId("pbc_3525142174")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return app.Delete(collection)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user