mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2025-10-28 16:55:32 +00:00
acces token table
This commit is contained in:
70
dbmodels/access_tokens.go
Normal file
70
dbmodels/access_tokens.go
Normal 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)
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
94
migrations/1747982298_access_tokens.go
Normal file
94
migrations/1747982298_access_tokens.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user