acces token table

This commit is contained in:
Simon Martens
2025-05-23 09:32:01 +02:00
parent b81d783e8c
commit 9e922aa49a
9 changed files with 271 additions and 30 deletions

70
dbmodels/access_tokens.go Normal file
View File

@@ -0,0 +1,70 @@
package dbmodels
import (
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/types"
)
var _ core.RecordProxy = (*AccessToken)(nil)
type AccessToken struct {
core.BaseRecordProxy
}
func NewAccessToken(record *core.Record) *AccessToken {
i := &AccessToken{}
i.SetProxyRecord(record)
return i
}
func (u *AccessToken) TableName() string {
return ACCESS_TOKENS_TABLE
}
func (u *AccessToken) Token() string {
return u.GetString(ACCESS_TOKENS_TOKEN_FIELD)
}
func (u *AccessToken) SetToken(token string) {
u.Set(ACCESS_TOKENS_TOKEN_FIELD, token)
}
func (u *AccessToken) User() string {
return u.GetString(ACCESS_TOKENS_USER_FIELD)
}
func (u *AccessToken) SetUser(userId string) {
u.Set(ACCESS_TOKENS_USER_FIELD, userId)
}
func (u *AccessToken) Created() string {
return u.GetString(CREATED_FIELD)
}
func (u *AccessToken) Updated() string {
return u.GetString(UPDATED_FIELD)
}
func (u *AccessToken) Expires() types.DateTime {
return u.GetDateTime(ACCESS_TOKENS_EXPIRES_FIELD)
}
func (u *AccessToken) SetExpires(expires types.DateTime) {
u.Set(ACCESS_TOKENS_EXPIRES_FIELD, expires)
}
func (u *AccessToken) URL() string {
return u.GetString(ACCESS_TOKENS_URL_FIELD)
}
func (u *AccessToken) SetURL(url string) {
u.Set(ACCESS_TOKENS_URL_FIELD, url)
}
func (u *AccessToken) Status() string {
return u.GetString(ACCESS_TOKENS_STATUS_FIELD)
}
func (u *AccessToken) SetStatus(status string) {
u.Set(ACCESS_TOKENS_STATUS_FIELD, status)
}

View File

@@ -4,6 +4,13 @@ import "github.com/pocketbase/pocketbase/tools/types"
var EDITORSTATE_VALUES = []string{"Unknown", "ToDo", "Seen", "Partially Edited", "Waiting", "Review", "Edited"}
var TOKEN_STATUS_VALUES = []string{
"active",
"expired",
"invalid",
"revoked",
}
var ITEM_TYPE_VALUES = []string{
"Original",
"Reproduktion",
@@ -425,13 +432,14 @@ var PUBLIC_LIST_RULE = types.Pointer("")
const (
FTS_LIMIT = 100000
PLACES_TABLE = "places"
AGENTS_TABLE = "agents"
SERIES_TABLE = "series"
ENTRIES_TABLE = "entries"
CONTENTS_TABLE = "contents"
ITEMS_TABLE = "items"
SESSIONS_TABLE = "sessions"
PLACES_TABLE = "places"
AGENTS_TABLE = "agents"
SERIES_TABLE = "series"
ENTRIES_TABLE = "entries"
CONTENTS_TABLE = "contents"
ITEMS_TABLE = "items"
SESSIONS_TABLE = "sessions"
ACCESS_TOKENS_TABLE = "access_tokens"
ID_FIELD = "id"
CREATED_FIELD = "created"
@@ -512,12 +520,22 @@ const (
SESSIONS_STATUS_FIELD = "status"
SESSIONS_PERSIST_FIELD = "persist"
USERS_TABLE = "users"
USERS_EMAIL_FIELD = "email"
USERS_SETTINGS_FIELD = "settings"
USERS_NAME_FIELD = "name"
USERS_ROLE_FIELD = "role"
USERS_AVATAR_FIELD = "avatar"
USERS_TABLE = "users"
USERS_EMAIL_FIELD = "email"
USERS_SETTINGS_FIELD = "settings"
USERS_NAME_FIELD = "name"
USERS_ROLE_FIELD = "role"
USERS_AVATAR_FIELD = "avatar"
USERS_VERIFIED_FIELD = "verified"
USERS_DEACTIVATED_FIELD = "deactivated"
ACCESS_TOKENS_TOKEN_FIELD = "token"
ACCESS_TOKENS_CSRF_FIELD = "csrf"
ACCESS_TOKENS_USER_FIELD = "user"
ACCESS_TOKENS_URL_FIELD = "url"
ACCESS_TOKENS_EXPIRES_FIELD = "expires"
ACCESS_TOKENS_LAST_ACCESS_FIELD = "accessed"
ACCESS_TOKENS_STATUS_FIELD = "status"
SESSION_COOKIE_NAME = "sid"
)

View File

@@ -67,6 +67,7 @@ func CreateSessionToken(
session.SetLastAccess(types.NowDateTime())
session.SetUserAgent(userAgent)
session.SetIP(ipAddress)
session.SetStatus(TOKEN_STATUS_VALUES[0]) // Active
if errSave := app.Save(session); errSave != nil {
app.Logger().Error("Failed to save session token record", "error", errSave, "userID", userID)

View File

@@ -115,6 +115,14 @@ func (u *Session) IsExpired() bool {
return u.Expires().IsZero() || u.Expires().Before(types.NowDateTime())
}
func (u *Session) Status() string {
return u.GetString(SESSIONS_STATUS_FIELD)
}
func (u *Session) SetStatus(status string) {
u.Set(SESSIONS_STATUS_FIELD, status)
}
func (u *Session) Fixed() FixedSession {
return FixedSession{
ID: u.Id,

View File

@@ -7,15 +7,16 @@ import (
)
type FixedUser struct {
Id string `json:"id"`
Email string `json:"email"`
Created types.DateTime `json:"created"`
Updated types.DateTime `json:"updated"`
Name string `json:"name"`
Role string `json:"role"`
Avatar string `json:"avatar"`
Verified bool `json:"verified"`
Settings string `json:"settings"`
Id string `json:"id"`
Email string `json:"email"`
Created types.DateTime `json:"created"`
Updated types.DateTime `json:"updated"`
Name string `json:"name"`
Role string `json:"role"`
Avatar string `json:"avatar"`
Verified bool `json:"verified"`
Settings string `json:"settings"`
Deactivated bool `json:"deactivated"`
}
var _ core.RecordProxy = (*Place)(nil)
@@ -72,15 +73,24 @@ func (u *User) SetAvatar(avatar *filesystem.File) {
u.Set(USERS_AVATAR_FIELD, avatar)
}
func (u *User) Deactivated() bool {
return u.GetBool(USERS_DEACTIVATED_FIELD)
}
func (u *User) SetDeactivated(deactivated bool) {
u.Set(USERS_DEACTIVATED_FIELD, deactivated)
}
func (u *User) Fixed() FixedUser {
return FixedUser{
Id: u.Id,
Email: u.Email(),
Created: u.Created(),
Updated: u.Updated(),
Name: u.Name(),
Role: u.Role(),
Avatar: u.Avatar(),
Verified: u.Verified(),
Id: u.Id,
Email: u.Email(),
Created: u.Created(),
Updated: u.Updated(),
Name: u.Name(),
Role: u.Role(),
Avatar: u.Avatar(),
Verified: u.Verified(),
Deactivated: u.Deactivated(),
}
}

View File

@@ -53,6 +53,8 @@ func Authenticated(app core.App) func(*core.RequestEvent) error {
slog.Debug("User session detected", "user", user.Id, "name", user.Name, "session", session.ID)
if session.IsExpired() {
// TODO: (Maybe) less rigid handling here: for creation or update of items forgive shortly
// expired tokens, if CSRF and everything else is a match.
slog.Warn("Session expired", "user", user.Id, "name", user.Name, "session", session.ID)
cache.Delete(cookie.Value)
go func() {
@@ -69,6 +71,29 @@ func Authenticated(app core.App) func(*core.RequestEvent) error {
e.Set("user", user)
e.Set("session", session)
token := e.Request.URL.Query().Get("token")
if token != "" {
record, err := app.FindFirstRecordByData(dbmodels.ACCESS_TOKENS_TABLE, dbmodels.ACCESS_TOKENS_TOKEN_FIELD, token)
if err != nil {
slog.Error("Failed to find access token", "token", token, "error", err)
return e.Next()
}
a := dbmodels.NewAccessToken(record)
if a.User() != "" {
r, err := app.FindRecordById(dbmodels.USERS_TABLE, a.User())
if err != nil {
slog.Error("Failed to find access token user", "user", a.User(), "error", err)
return e.Next()
}
u := dbmodels.NewUser(r)
e.Set("access_token_user", u)
}
e.Set("access_token", a)
}
return e.Next()
}
}

View File

@@ -87,6 +87,13 @@ func sessionTokensFields(usersCollectionId string) core.FieldsList {
Name: dbmodels.SESSIONS_PERSIST_FIELD,
Presentable: true,
},
&core.SelectField{
Name: dbmodels.SESSIONS_STATUS_FIELD,
Required: true,
Presentable: true,
MaxSelect: 1,
Values: dbmodels.TOKEN_STATUS_VALUES,
},
)
return fields

View File

@@ -30,8 +30,15 @@ func init() {
Values: dbmodels.USER_ROLES,
}
deactivatedField := &core.BoolField{
Name: dbmodels.USERS_DEACTIVATED_FIELD,
Required: false,
Presentable: false,
}
collection.Fields.Add(settingsField)
collection.Fields.Add(roleField)
collection.Fields.Add(deactivatedField)
app.Logger().Info("Adding 'settings' JSON field to 'users' collection", "collectionId", collection.Id)
@@ -50,6 +57,7 @@ func init() {
collection.Fields.RemoveByName(dbmodels.USERS_SETTINGS_FIELD)
collection.Fields.RemoveByName(dbmodels.USERS_ROLE_FIELD)
collection.Fields.RemoveByName(dbmodels.USERS_DEACTIVATED_FIELD)
if err := app.Save(collection); err != nil {
app.Logger().Warn("Failed to remove 'settings' field during rollback (it might not exist)",
"collectionId", collection.Id,

View File

@@ -0,0 +1,94 @@
package migrations
import (
"strings"
"github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
usersCollection, err := app.FindCollectionByNameOrId("users")
if err != nil {
app.Logger().Error("Failed to find 'users' collection for sessionTokens migration", "error", err)
return err
}
collection := accessTokensTable()
fields := accessTokensFields(usersCollection.Id)
dbmodels.SetCreatedUpdatedFields(&fields)
collection.Fields = fields
dbmodels.AddIndex(collection, dbmodels.ACCESS_TOKENS_TOKEN_FIELD, true)
dbmodels.AddIndex(collection, dbmodels.ACCESS_TOKENS_USER_FIELD, false)
dbmodels.AddIndex(collection, dbmodels.ACCESS_TOKENS_EXPIRES_FIELD, false)
dbmodels.AddIndex(collection, dbmodels.ACCESS_TOKENS_LAST_ACCESS_FIELD, false)
dbmodels.AddIndex(collection, dbmodels.ACCESS_TOKENS_URL_FIELD, false)
dbmodels.AddIndex(collection, dbmodels.ACCESS_TOKENS_STATUS_FIELD, false)
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId(dbmodels.ACCESS_TOKENS_TABLE)
if err != nil {
if strings.Contains(err.Error(), "not found") || strings.Contains(err.Error(), "no rows in result set") {
return nil
}
app.Logger().Error("Failed to find collection for deletion", "collection", dbmodels.ACCESS_TOKENS_TABLE, "error", err)
return err
}
return app.Delete(collection)
})
}
func accessTokensTable() *core.Collection {
collection := core.NewBaseCollection(dbmodels.ACCESS_TOKENS_TABLE)
return collection
}
func accessTokensFields(usersCollectionId string) core.FieldsList {
fields := core.NewFieldsList(
&core.TextField{
Name: dbmodels.ACCESS_TOKENS_TOKEN_FIELD,
Required: true,
Presentable: false,
},
&core.TextField{
Name: dbmodels.ACCESS_TOKENS_CSRF_FIELD,
Required: true,
Presentable: false,
},
&core.RelationField{
Name: dbmodels.ACCESS_TOKENS_USER_FIELD,
Required: true,
CollectionId: usersCollectionId,
CascadeDelete: true,
Presentable: true,
},
&core.DateField{
Name: dbmodels.ACCESS_TOKENS_EXPIRES_FIELD,
Required: true,
Presentable: true,
},
&core.DateField{
Name: dbmodels.ACCESS_TOKENS_LAST_ACCESS_FIELD,
Presentable: false,
},
&core.TextField{
Name: dbmodels.ACCESS_TOKENS_URL_FIELD,
Presentable: true,
},
&core.SelectField{
Name: dbmodels.ACCESS_TOKENS_STATUS_FIELD,
Required: true,
Presentable: true,
MaxSelect: 1,
Values: dbmodels.TOKEN_STATUS_VALUES,
},
)
return fields
}