mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2025-12-16 06:15:29 +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 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{
|
var ITEM_TYPE_VALUES = []string{
|
||||||
"Original",
|
"Original",
|
||||||
"Reproduktion",
|
"Reproduktion",
|
||||||
@@ -425,13 +432,14 @@ var PUBLIC_LIST_RULE = types.Pointer("")
|
|||||||
const (
|
const (
|
||||||
FTS_LIMIT = 100000
|
FTS_LIMIT = 100000
|
||||||
|
|
||||||
PLACES_TABLE = "places"
|
PLACES_TABLE = "places"
|
||||||
AGENTS_TABLE = "agents"
|
AGENTS_TABLE = "agents"
|
||||||
SERIES_TABLE = "series"
|
SERIES_TABLE = "series"
|
||||||
ENTRIES_TABLE = "entries"
|
ENTRIES_TABLE = "entries"
|
||||||
CONTENTS_TABLE = "contents"
|
CONTENTS_TABLE = "contents"
|
||||||
ITEMS_TABLE = "items"
|
ITEMS_TABLE = "items"
|
||||||
SESSIONS_TABLE = "sessions"
|
SESSIONS_TABLE = "sessions"
|
||||||
|
ACCESS_TOKENS_TABLE = "access_tokens"
|
||||||
|
|
||||||
ID_FIELD = "id"
|
ID_FIELD = "id"
|
||||||
CREATED_FIELD = "created"
|
CREATED_FIELD = "created"
|
||||||
@@ -512,12 +520,22 @@ const (
|
|||||||
SESSIONS_STATUS_FIELD = "status"
|
SESSIONS_STATUS_FIELD = "status"
|
||||||
SESSIONS_PERSIST_FIELD = "persist"
|
SESSIONS_PERSIST_FIELD = "persist"
|
||||||
|
|
||||||
USERS_TABLE = "users"
|
USERS_TABLE = "users"
|
||||||
USERS_EMAIL_FIELD = "email"
|
USERS_EMAIL_FIELD = "email"
|
||||||
USERS_SETTINGS_FIELD = "settings"
|
USERS_SETTINGS_FIELD = "settings"
|
||||||
USERS_NAME_FIELD = "name"
|
USERS_NAME_FIELD = "name"
|
||||||
USERS_ROLE_FIELD = "role"
|
USERS_ROLE_FIELD = "role"
|
||||||
USERS_AVATAR_FIELD = "avatar"
|
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"
|
SESSION_COOKIE_NAME = "sid"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ func CreateSessionToken(
|
|||||||
session.SetLastAccess(types.NowDateTime())
|
session.SetLastAccess(types.NowDateTime())
|
||||||
session.SetUserAgent(userAgent)
|
session.SetUserAgent(userAgent)
|
||||||
session.SetIP(ipAddress)
|
session.SetIP(ipAddress)
|
||||||
|
session.SetStatus(TOKEN_STATUS_VALUES[0]) // Active
|
||||||
|
|
||||||
if errSave := app.Save(session); errSave != nil {
|
if errSave := app.Save(session); errSave != nil {
|
||||||
app.Logger().Error("Failed to save session token record", "error", errSave, "userID", userID)
|
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())
|
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 {
|
func (u *Session) Fixed() FixedSession {
|
||||||
return FixedSession{
|
return FixedSession{
|
||||||
ID: u.Id,
|
ID: u.Id,
|
||||||
|
|||||||
@@ -7,15 +7,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type FixedUser struct {
|
type FixedUser struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Created types.DateTime `json:"created"`
|
Created types.DateTime `json:"created"`
|
||||||
Updated types.DateTime `json:"updated"`
|
Updated types.DateTime `json:"updated"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Avatar string `json:"avatar"`
|
Avatar string `json:"avatar"`
|
||||||
Verified bool `json:"verified"`
|
Verified bool `json:"verified"`
|
||||||
Settings string `json:"settings"`
|
Settings string `json:"settings"`
|
||||||
|
Deactivated bool `json:"deactivated"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ core.RecordProxy = (*Place)(nil)
|
var _ core.RecordProxy = (*Place)(nil)
|
||||||
@@ -72,15 +73,24 @@ func (u *User) SetAvatar(avatar *filesystem.File) {
|
|||||||
u.Set(USERS_AVATAR_FIELD, avatar)
|
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 {
|
func (u *User) Fixed() FixedUser {
|
||||||
return FixedUser{
|
return FixedUser{
|
||||||
Id: u.Id,
|
Id: u.Id,
|
||||||
Email: u.Email(),
|
Email: u.Email(),
|
||||||
Created: u.Created(),
|
Created: u.Created(),
|
||||||
Updated: u.Updated(),
|
Updated: u.Updated(),
|
||||||
Name: u.Name(),
|
Name: u.Name(),
|
||||||
Role: u.Role(),
|
Role: u.Role(),
|
||||||
Avatar: u.Avatar(),
|
Avatar: u.Avatar(),
|
||||||
Verified: u.Verified(),
|
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)
|
slog.Debug("User session detected", "user", user.Id, "name", user.Name, "session", session.ID)
|
||||||
|
|
||||||
if session.IsExpired() {
|
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)
|
slog.Warn("Session expired", "user", user.Id, "name", user.Name, "session", session.ID)
|
||||||
cache.Delete(cookie.Value)
|
cache.Delete(cookie.Value)
|
||||||
go func() {
|
go func() {
|
||||||
@@ -69,6 +71,29 @@ func Authenticated(app core.App) func(*core.RequestEvent) error {
|
|||||||
e.Set("user", user)
|
e.Set("user", user)
|
||||||
e.Set("session", session)
|
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()
|
return e.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,6 +87,13 @@ func sessionTokensFields(usersCollectionId string) core.FieldsList {
|
|||||||
Name: dbmodels.SESSIONS_PERSIST_FIELD,
|
Name: dbmodels.SESSIONS_PERSIST_FIELD,
|
||||||
Presentable: true,
|
Presentable: true,
|
||||||
},
|
},
|
||||||
|
&core.SelectField{
|
||||||
|
Name: dbmodels.SESSIONS_STATUS_FIELD,
|
||||||
|
Required: true,
|
||||||
|
Presentable: true,
|
||||||
|
MaxSelect: 1,
|
||||||
|
Values: dbmodels.TOKEN_STATUS_VALUES,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
return fields
|
return fields
|
||||||
|
|||||||
@@ -30,8 +30,15 @@ func init() {
|
|||||||
Values: dbmodels.USER_ROLES,
|
Values: dbmodels.USER_ROLES,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deactivatedField := &core.BoolField{
|
||||||
|
Name: dbmodels.USERS_DEACTIVATED_FIELD,
|
||||||
|
Required: false,
|
||||||
|
Presentable: false,
|
||||||
|
}
|
||||||
|
|
||||||
collection.Fields.Add(settingsField)
|
collection.Fields.Add(settingsField)
|
||||||
collection.Fields.Add(roleField)
|
collection.Fields.Add(roleField)
|
||||||
|
collection.Fields.Add(deactivatedField)
|
||||||
|
|
||||||
app.Logger().Info("Adding 'settings' JSON field to 'users' collection", "collectionId", collection.Id)
|
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_SETTINGS_FIELD)
|
||||||
collection.Fields.RemoveByName(dbmodels.USERS_ROLE_FIELD)
|
collection.Fields.RemoveByName(dbmodels.USERS_ROLE_FIELD)
|
||||||
|
collection.Fields.RemoveByName(dbmodels.USERS_DEACTIVATED_FIELD)
|
||||||
if err := app.Save(collection); err != nil {
|
if err := app.Save(collection); err != nil {
|
||||||
app.Logger().Warn("Failed to remove 'settings' field during rollback (it might not exist)",
|
app.Logger().Warn("Failed to remove 'settings' field during rollback (it might not exist)",
|
||||||
"collectionId", collection.Id,
|
"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