---
Still pending some handlers to display audit logs but I wanted to ensure
we get record keeping in place asap as more and more users register.
accounts/userfetch.go | 65 +++++
api/graph/schema.graphqls | 6 +-
api/graph/schema.resolvers.go | 459 +++++++++++++++++++++++++++++++---
models/audit_log.go | 65 +++++
models/models.go | 5 -
5 files changed, 554 insertions(+), 46 deletions(-)
create mode 100644 models/audit_log.go
diff --git a/accounts/userfetch.go b/accounts/userfetch.go
index 6b8bba4..b509086 100644
--- a/accounts/userfetch.go
+++ b/accounts/userfetch.go
@@ -143,7 +143,20 @@ func (u *UserFetch) ProcessSuccessfulLogout(c echo.Context) error {
// ProcessSuccessfulPasswordChange handle tasks after user is logged in
func (u *UserFetch) ProcessSuccessfulPasswordChange(c echo.Context) error {
+ gctx := c.(*server.Context)
lt := localizer.GetSessionLocalizer(c)
+ err := models.RecordAuditLog(
+ c.Request().Context(),
+ int(gctx.User.GetID()),
+ c.RealIP(),
+ models.LOG_ACCT_PASSWORD_CHANGE,
+ "Successfully changed password.",
+ nil,
+ )
+ if err != nil {
+ return err
+ }
+
messages.Success(c, lt.Translate("You've successfully updated your password."))
return nil
}
@@ -151,6 +164,19 @@ func (u *UserFetch) ProcessSuccessfulPasswordChange(c echo.Context) error {
// ProcessSuccessfulPasswordReset handle tasks after user is logged in
func (u *UserFetch) ProcessSuccessfulPasswordReset(c echo.Context) error {
lt := localizer.GetSessionLocalizer(c)
+ user := c.Get("user").(*models.User)
+ err := models.RecordAuditLog(
+ c.Request().Context(),
+ int(user.ID),
+ c.RealIP(),
+ models.LOG_ACCT_PASSWORD_RESET,
+ "Successfully reset password.",
+ nil,
+ )
+ if err != nil {
+ return err
+ }
+
messages.Success(c, lt.Translate("You've successfully updated your password."))
return nil
}
@@ -158,6 +184,19 @@ func (u *UserFetch) ProcessSuccessfulPasswordReset(c echo.Context) error {
// ProcessSuccessfulEmailUpdate handle tasks after user is logged in
func (u *UserFetch) ProcessSuccessfulEmailUpdate(c echo.Context) error {
lt := localizer.GetSessionLocalizer(c)
+ user := c.Get("user").(*models.User)
+ err := models.RecordAuditLog(
+ c.Request().Context(),
+ int(user.ID),
+ c.RealIP(),
+ models.LOG_ACCT_EMAIL_UPDATE,
+ "Successfully updated account email.",
+ nil,
+ )
+ if err != nil {
+ return err
+ }
+
messages.Success(c, lt.Translate("You've successfully updated your email address."))
return nil
}
@@ -180,6 +219,19 @@ func (u *UserFetch) ProcessSuccessfulEmailConfirmation(c echo.Context) error {
if err != nil {
gctx.Server.Logger().Printf("Error queueing SendySubscribeTask: %v", err)
}
+
+ err = models.RecordAuditLog(
+ c.Request().Context(),
+ int(user.ID),
+ c.RealIP(),
+ models.LOG_ACCT_EMAIL_CONF,
+ "Successfully confirmed account email.",
+ nil,
+ )
+ if err != nil {
+ return err
+ }
+
messages.Success(c, lt.Translate("You've successfully confirmed your email address."))
return nil
}
@@ -234,6 +286,19 @@ func (u *UserFetch) ProcessSuccessfulLogin(c echo.Context, user gobwebs.User) er
links.SetUserSession(c, lUser)
lt := localizer.GetSessionLocalizer(c)
+
+ err := models.RecordAuditLog(
+ c.Request().Context(),
+ int(lUser.ID),
+ c.RealIP(),
+ models.LOG_ACCT_LOGIN,
+ "Successful account login.",
+ nil,
+ )
+ if err != nil {
+ return err
+ }
+
messages.Success(c, lt.Translate("Successful login."))
return nil
}
diff --git a/api/graph/schema.graphqls b/api/graph/schema.graphqls
index 4612125..79f72eb 100644
--- a/api/graph/schema.graphqls
+++ b/api/graph/schema.graphqls
@@ -814,6 +814,9 @@ type Mutation {
"Create a new organization"
addOrganization(input: OrganizationInput!): Organization! @access(scope: ORGS, kind: RW)
+ "Update organization details"
+ updateOrganization(input: UpdateOrganizationInput): Organization! @access(scope: ORGS, kind: RW)
+
"Add/Edit/Delete organization link. Also used edit/delete notes"
addLink(input: LinkInput): OrgLink! @access(scope: LINKS, kind: RW)
updateLink(input: UpdateLinkInput): OrgLink! @access(scope: LINKS, kind: RW)
@@ -834,9 +837,6 @@ type Mutation {
"Update user profile"
updateProfile(input: ProfileInput): User! @access(scope: PROFILE, kind: RW)
- "Update organization details"
- updateOrganization(input: UpdateOrganizationInput): Organization! @access(scope: ORGS, kind: RW)
-
"Manage custom domains for an organization"
addDomain(input: DomainInput!): Domain! @access(scope: DOMAINS, kind: RW)
deleteDomain(id: Int!): DeletePayload! @access(scope: DOMAINS, kind: RW)
diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go
index d82d48b..bd8d6ed 100644
--- a/api/graph/schema.resolvers.go
+++ b/api/graph/schema.resolvers.go
@@ -39,7 +39,6 @@ import (
"golang.org/x/image/draw"
"golang.org/x/net/idna"
"netlandish.com/x/gobwebs"
- auditlog "netlandish.com/x/gobwebs-auditlog"
oauth2 "netlandish.com/x/gobwebs-oauth2"
gaccounts "netlandish.com/x/gobwebs/accounts"
gcore "netlandish.com/x/gobwebs/core"
@@ -89,6 +88,7 @@ func (r *mutationResolver) AddOrganization(ctx context.Context, input model.Orga
return nil, valid.ErrAuthorization
}
user := tokenUser.User.(*models.User)
+ c := server.EchoForContext(ctx)
lang := links.GetLangFromRequest(server.EchoForContext(ctx).Request(), user)
lt := localizer.GetLocalizer(lang)
@@ -182,6 +182,20 @@ func (r *mutationResolver) AddOrganization(ctx context.Context, input model.Orga
return nil, err
}
+ mdata := make(map[string]any)
+ mdata["org_id"] = org.ID
+ err = models.RecordAuditLog(
+ ctx,
+ int(user.ID),
+ c.RealIP(),
+ models.LOG_ORG_ADDED,
+ fmt.Sprintf("Added organization '%s' (%d)", org.Slug, org.ID),
+ mdata,
+ )
+ if err != nil {
+ return nil, err
+ }
+
return org, nil
}
@@ -352,6 +366,21 @@ func (r *mutationResolver) AddLink(ctx context.Context, input *model.LinkInput)
}
}
+ c := server.EchoForContext(ctx)
+ mdata := make(map[string]any)
+ mdata["org_id"] = org.ID
+ err = models.RecordAuditLog(
+ ctx,
+ userID,
+ c.RealIP(),
+ models.LOG_BOOKMARK_ADDED,
+ fmt.Sprintf("Added bookmark '%s'", OrgLink.Hash),
+ mdata,
+ )
+ if err != nil {
+ return nil, err
+ }
+
return OrgLink, nil
}
@@ -545,6 +574,28 @@ func (r *mutationResolver) UpdateLink(ctx context.Context, input *model.UpdateLi
return nil, err
}
}
+
+ c := server.EchoForContext(ctx)
+ mdata := make(map[string]any)
+ mdata["org_id"] = org.ID
+ ltype := models.LOG_BOOKMARK_UPDATED
+ ldet := "bookmark"
+ if orgLink.Type == models.NoteType {
+ ltype = models.LOG_NOTE_UPDATED
+ ldet = "note"
+ }
+ err = models.RecordAuditLog(
+ ctx,
+ int(user.ID),
+ c.RealIP(),
+ ltype,
+ fmt.Sprintf("Updated %s '%s'", ldet, orgLink.Hash),
+ mdata,
+ )
+ if err != nil {
+ return nil, err
+ }
+
return orgLink, nil
}
@@ -580,28 +631,28 @@ func (r *mutationResolver) DeleteLink(ctx context.Context, hash string) (*model.
}
link := orgLinks[0]
- // If the user is not the link creator, verify org write permissions
- if link.UserID != int(user.ID) {
- opts = &database.FilterOptions{
- Filter: sq.And{
- sq.Expr("o.id = ?", link.OrgID),
- sq.Expr("o.is_active = true"),
- },
- Limit: 1,
- }
+ opts = &database.FilterOptions{
+ Filter: sq.And{
+ sq.Expr("o.id = ?", link.OrgID),
+ sq.Expr("o.is_active = true"),
+ },
+ Limit: 1,
+ }
- orgs, err := models.GetOrganizations(ctx, opts)
- if err != nil {
- return nil, err
- }
+ orgs, err := models.GetOrganizations(ctx, opts)
+ if err != nil {
+ return nil, err
+ }
- if len(orgs) == 0 {
- validator.Error("%s", lt.Translate("This user is not allowed to perform this action")).
- WithCode(valid.ErrNotFoundCode)
- return nil, nil
- }
+ if len(orgs) == 0 {
+ validator.Error("%s", lt.Translate("This user is not allowed to perform this action")).
+ WithCode(valid.ErrNotFoundCode)
+ return nil, nil
+ }
+ org := orgs[0]
- org := orgs[0]
+ // If the user is not the link creator, verify org write permissions
+ if link.UserID != int(user.ID) {
if !org.CanWrite(ctx, user) {
validator.Error("%s", lt.Translate("This user is not allowed to perform this action")).
WithCode(valid.ErrNotFoundCode)
@@ -628,6 +679,28 @@ func (r *mutationResolver) DeleteLink(ctx context.Context, hash string) (*model.
return nil, err
}
}
+
+ c := server.EchoForContext(ctx)
+ mdata := make(map[string]any)
+ mdata["org_id"] = org.ID
+ ltype := models.LOG_BOOKMARK_DELETED
+ ldet := "bookmark"
+ if link.Type == models.NoteType {
+ ltype = models.LOG_NOTE_DELETED
+ ldet = "note"
+ }
+ err = models.RecordAuditLog(
+ ctx,
+ int(user.ID),
+ c.RealIP(),
+ ltype,
+ fmt.Sprintf("Deleted %s '%s'", ldet, link.Hash),
+ mdata,
+ )
+ if err != nil {
+ return nil, err
+ }
+
deletePayload.Success = true
deletePayload.ObjectID = deletedID
return deletePayload, nil
@@ -929,6 +1002,22 @@ func (r *mutationResolver) AddMember(ctx context.Context, input *model.MemberInp
if err != nil {
return nil, err
}
+
+ mdata := make(map[string]any)
+ mdata["org_id"] = org.ID
+ mdata["user_id"] = user.ID
+ err = models.RecordAuditLog(
+ ctx,
+ int(currentUser.ID),
+ c.RealIP(),
+ models.LOG_MEMBER_ADDED,
+ fmt.Sprintf("Added member %s", user.Email),
+ mdata,
+ )
+ if err != nil {
+ return nil, err
+ }
+
addMemberPayload.Success = true
return addMemberPayload, nil
}
@@ -977,8 +1066,9 @@ func (r *mutationResolver) DeleteMember(ctx context.Context, orgSlug string, ema
return nil, nil
}
+ email = strings.ToLower(email)
opts := &database.FilterOptions{
- Filter: sq.Eq{"u.email": strings.ToLower(email)},
+ Filter: sq.Eq{"u.email": email},
Limit: 1,
}
@@ -1015,6 +1105,22 @@ func (r *mutationResolver) DeleteMember(ctx context.Context, orgSlug string, ema
return nil, err
}
}
+
+ mdata := make(map[string]any)
+ mdata["org_id"] = org.ID
+ mdata["user_id"] = duser.ID
+ err = models.RecordAuditLog(
+ ctx,
+ int(user.ID),
+ c.RealIP(),
+ models.LOG_MEMBER_REMOVED,
+ fmt.Sprintf("Removed member %s", email),
+ mdata,
+ )
+ if err != nil {
+ return nil, err
+ }
+
addMemberPayload.Success = true
addMemberPayload.Message = lt.Translate("The member was removed successfully")
}
@@ -1126,6 +1232,22 @@ func (r *mutationResolver) ConfirmMember(ctx context.Context, key string) (*mode
if err != nil {
return nil, err
}
+
+ c := server.EchoForContext(ctx)
+ mdata := make(map[string]any)
+ mdata["org_id"] = org.ID
+ err = models.RecordAuditLog(
+ ctx,
+ int(user.ID),
+ c.RealIP(),
+ models.LOG_MEMBER_CONFIRMED,
+ fmt.Sprintf("Confirmed member %s", user.Email),
+ mdata,
+ )
+ if err != nil {
+ return nil, err
+ }
+
addMemberPayload := &model.AddMemberPayload{
Success: true,
Message: lt.Translate("The user was added successfully"),
@@ -1585,6 +1707,21 @@ func (r *mutationResolver) UpdateProfile(ctx context.Context, input *model.Profi
}
}
+ c := server.EchoForContext(ctx)
+ mdata := make(map[string]any)
+ mdata["org_id"] = personalOrg.ID
+ err = models.RecordAuditLog(
+ ctx,
+ int(user.ID),
+ c.RealIP(),
+ models.LOG_PROFILE_UPDATED,
+ fmt.Sprintf("Updated profile"),
+ mdata,
+ )
+ if err != nil {
+ return nil, err
+ }
+
return user, nil
}
@@ -1764,6 +1901,21 @@ func (r *mutationResolver) UpdateOrganization(ctx context.Context, input *model.
return nil, err
}
+ c := server.EchoForContext(ctx)
+ mdata := make(map[string]any)
+ mdata["org_id"] = org.ID
+ err = models.RecordAuditLog(
+ ctx,
+ int(user.ID),
+ c.RealIP(),
+ models.LOG_ORG_UPDATED,
+ fmt.Sprintf("Updated organization '%s' (%d)", org.Slug, org.ID),
+ mdata,
+ )
+ if err != nil {
+ return nil, err
+ }
+
return org, nil
}
@@ -1901,6 +2053,23 @@ func (r *mutationResolver) AddDomain(ctx context.Context, input model.DomainInpu
return nil, err
}
}
+
+ c := server.EchoForContext(ctx)
+ mdata := make(map[string]any)
+ mdata["org_id"] = org.ID
+ mdata["domain_id"] = domain.ID
+ err = models.RecordAuditLog(
+ ctx,
+ int(user.ID),
+ c.RealIP(),
+ models.LOG_DOMAIN_ADDED,
+ fmt.Sprintf("Added domain '%s'", domain.LookupName),
+ mdata,
+ )
+ if err != nil {
+ return nil, err
+ }
+
return domain, nil
}
@@ -1969,6 +2138,22 @@ func (r *mutationResolver) DeleteDomain(ctx context.Context, id int) (*model.Del
return nil, err
}
+ c := server.EchoForContext(ctx)
+ mdata := make(map[string]any)
+ mdata["org_id"] = domain.OrgID
+ mdata["domain_id"] = domain.ID
+ err = models.RecordAuditLog(
+ ctx,
+ int(user.ID),
+ c.RealIP(),
+ models.LOG_DOMAIN_DELETED,
+ fmt.Sprintf("Deleted domain '%s'", domain.LookupName),
+ mdata,
+ )
+ if err != nil {
+ return nil, err
+ }
+
deletePayload.Success = true
deletePayload.ObjectID = fmt.Sprint(id)
return deletePayload, nil
@@ -2170,14 +2355,16 @@ func (r *mutationResolver) AddLinkShort(ctx context.Context, input *model.LinkSh
}
}
- alog := auditlog.New(
+ mdata := make(map[string]any)
+ mdata["org_id"] = org.ID
+ err = models.RecordAuditLog(
+ ctx,
int(user.ID),
c.RealIP(),
models.LOG_SHORT_ADDED,
- fmt.Sprintf("Added short link '%s'", linkShort.ShortCode),
+ fmt.Sprintf("Added short link '%s' (%d)", linkShort.ShortCode, linkShort.ID),
+ mdata,
)
- alog.AddMetadata("org_id", org.ID)
- err = alog.Store(ctx)
if err != nil {
return nil, err
}
@@ -2192,6 +2379,7 @@ func (r *mutationResolver) UpdateLinkShort(ctx context.Context, input *model.Upd
return nil, valid.ErrAuthorization
}
user := tokenUser.User.(*models.User)
+ c := server.EchoForContext(ctx)
lang := links.GetLangFromRequest(server.EchoForContext(ctx).Request(), user)
lt := localizer.GetLocalizer(lang)
@@ -2362,6 +2550,20 @@ func (r *mutationResolver) UpdateLinkShort(ctx context.Context, input *model.Upd
}
}
+ mdata := make(map[string]any)
+ mdata["org_id"] = org.ID
+ err = models.RecordAuditLog(
+ ctx,
+ int(user.ID),
+ c.RealIP(),
+ models.LOG_SHORT_UPDATED,
+ fmt.Sprintf("Updated short link '%s' (%d)", linkShort.ShortCode, linkShort.ID),
+ mdata,
+ )
+ if err != nil {
+ return nil, err
+ }
+
return linkShort, nil
}
@@ -2372,6 +2574,7 @@ func (r *mutationResolver) DeleteLinkShort(ctx context.Context, id int) (*model.
return nil, valid.ErrAuthorization
}
user := tokenUser.User.(*models.User)
+ c := server.EchoForContext(ctx)
lang := links.GetLangFromRequest(server.EchoForContext(ctx).Request(), user)
lt := localizer.GetLocalizer(lang)
deletePayload := &model.DeletePayload{
@@ -2413,6 +2616,21 @@ func (r *mutationResolver) DeleteLinkShort(ctx context.Context, id int) (*model.
if err != nil {
return nil, err
}
+
+ mdata := make(map[string]any)
+ mdata["org_id"] = org.ID
+ err = models.RecordAuditLog(
+ ctx,
+ int(user.ID),
+ c.RealIP(),
+ models.LOG_SHORT_DELETED,
+ fmt.Sprintf("Deleted short link '%s' (%d)", link.ShortCode, link.ID),
+ mdata,
+ )
+ if err != nil {
+ return nil, err
+ }
+
deletePayload.Success = true
deletePayload.ObjectID = deletedID
return deletePayload, nil
@@ -2714,6 +2932,22 @@ func (r *mutationResolver) AddListing(ctx context.Context, input *model.AddListi
}
}
+ c := server.EchoForContext(ctx)
+ mdata := make(map[string]any)
+ mdata["org_id"] = org.ID
+ mdata["list_id"] = listing.ID
+ err = models.RecordAuditLog(
+ ctx,
+ int(user.ID),
+ c.RealIP(),
+ models.LOG_LIST_ADDED,
+ fmt.Sprintf("Added link listing '%s' (%d)", listing.Slug, listing.ID),
+ mdata,
+ )
+ if err != nil {
+ return nil, err
+ }
+
return listing, nil
}
@@ -2799,6 +3033,24 @@ func (r *mutationResolver) AddListingLink(ctx context.Context, input *model.AddL
if err != nil {
return nil, err
}
+
+ c := server.EchoForContext(ctx)
+ mdata := make(map[string]any)
+ mdata["org_id"] = org.ID
+ mdata["list_id"] = listing.ID
+ mdata["list_link_id"] = listingLink.ID
+ err = models.RecordAuditLog(
+ ctx,
+ int(user.ID),
+ c.RealIP(),
+ models.LOG_LIST_LINK_ADDED,
+ fmt.Sprintf("Added listing entry '%s' (%d)", listingLink.Title, listingLink.ID),
+ mdata,
+ )
+ if err != nil {
+ return nil, err
+ }
+
return listingLink, nil
}
@@ -3126,6 +3378,23 @@ func (r *mutationResolver) UpdateListing(ctx context.Context, input *model.Updat
return nil, err
}
}
+
+ c := server.EchoForContext(ctx)
+ mdata := make(map[string]any)
+ mdata["org_id"] = org.ID
+ mdata["list_id"] = listing.ID
+ err = models.RecordAuditLog(
+ ctx,
+ int(user.ID),
+ c.RealIP(),
+ models.LOG_LIST_UPDATED,
+ fmt.Sprintf("Updated link listing '%s' (%d)", listing.Slug, listing.ID),
+ mdata,
+ )
+ if err != nil {
+ return nil, err
+ }
+
return listing, nil
}
@@ -3199,6 +3468,24 @@ func (r *mutationResolver) UpdateListingLink(ctx context.Context, input *model.U
if err != nil {
return nil, err
}
+
+ c := server.EchoForContext(ctx)
+ mdata := make(map[string]any)
+ mdata["org_id"] = org.ID
+ mdata["list_id"] = listingLink.ListingID
+ mdata["list_link_id"] = listingLink.ID
+ err = models.RecordAuditLog(
+ ctx,
+ int(user.ID),
+ c.RealIP(),
+ models.LOG_LIST_LINK_UPDATED,
+ fmt.Sprintf("Updated listing entry '%s' (%d)", listingLink.Title, listingLink.ID),
+ mdata,
+ )
+ if err != nil {
+ return nil, err
+ }
+
return listingLink, nil
}
@@ -3251,6 +3538,23 @@ func (r *mutationResolver) DeleteListing(ctx context.Context, id int) (*model.De
if err != nil {
return nil, err
}
+
+ c := server.EchoForContext(ctx)
+ mdata := make(map[string]any)
+ mdata["org_id"] = org.ID
+ mdata["list_id"] = listing.ID
+ err = models.RecordAuditLog(
+ ctx,
+ int(user.ID),
+ c.RealIP(),
+ models.LOG_LIST_DELETED,
+ fmt.Sprintf("Deleted link listing '%s' (%d)", listing.Slug, listing.ID),
+ mdata,
+ )
+ if err != nil {
+ return nil, err
+ }
+
deletePayload.Success = true
deletePayload.ObjectID = deletedID
return deletePayload, nil
@@ -3305,6 +3609,24 @@ func (r *mutationResolver) DeleteListingLink(ctx context.Context, id int) (*mode
if err != nil {
return nil, err
}
+
+ c := server.EchoForContext(ctx)
+ mdata := make(map[string]any)
+ mdata["org_id"] = org.ID
+ mdata["list_id"] = listingLink.ListingID
+ mdata["list_link_id"] = listingLink.ID
+ err = models.RecordAuditLog(
+ ctx,
+ int(user.ID),
+ c.RealIP(),
+ models.LOG_LIST_LINK_DELETED,
+ fmt.Sprintf("Updated listing entry '%s' (%d)", listingLink.Title, listingLink.ID),
+ mdata,
+ )
+ if err != nil {
+ return nil, err
+ }
+
deletePayload.Success = true
deletePayload.ObjectID = deletedID
return deletePayload, nil
@@ -3580,6 +3902,23 @@ func (r *mutationResolver) AddQRCode(ctx context.Context, input model.AddQRCodeI
return nil, err
}
}
+
+ mdata := make(map[string]any)
+ mdata["org_id"] = org.ID
+ mdata["qrcode_id"] = qr.ID
+ mdata["qrcode_type"] = qr.CodeType
+ err = models.RecordAuditLog(
+ ctx,
+ int(user.ID),
+ c.RealIP(),
+ models.LOG_QRCODE_ADDED,
+ fmt.Sprintf("Added QR code '%s', type: %s (%d)", qr.Title, qr.CodeType, qr.ID),
+ mdata,
+ )
+ if err != nil {
+ return nil, err
+ }
+
return qr, nil
}
@@ -3632,6 +3971,24 @@ func (r *mutationResolver) DeleteQRCode(ctx context.Context, id int) (*model.Del
if err != nil {
return nil, err
}
+
+ c := server.EchoForContext(ctx)
+ mdata := make(map[string]any)
+ mdata["org_id"] = org.ID
+ mdata["qrcode_id"] = qrCode.ID
+ mdata["qrcode_type"] = qrCode.CodeType
+ err = models.RecordAuditLog(
+ ctx,
+ int(user.ID),
+ c.RealIP(),
+ models.LOG_QRCODE_DELETED,
+ fmt.Sprintf("Deleted QR code '%s', type: %s (%d)", qrCode.Title, qrCode.CodeType, qrCode.ID),
+ mdata,
+ )
+ if err != nil {
+ return nil, err
+ }
+
deletePayload.Success = true
deletePayload.ObjectID = deletedID
return deletePayload, nil
@@ -3677,21 +4034,31 @@ func (r *mutationResolver) Follow(ctx context.Context, orgSlug string) (*model.F
return nil, err
}
- if len(follows) > 0 {
- validator.Error("%s", lt.Translate("This user already follows this org")).
- WithField("orgSlug").
- WithCode(valid.ErrValidationCode)
- return nil, nil
- }
+ if len(follows) == 0 {
+ follow := &models.Follower{
+ UserID: int(user.ID),
+ OrgID: org.ID,
+ }
- follow := &models.Follower{
- UserID: int(user.ID),
- OrgID: org.ID,
- }
+ err = follow.Store(ctx)
+ if err != nil {
+ return nil, err
+ }
- err = follow.Store(ctx)
- if err != nil {
- return nil, err
+ c := server.EchoForContext(ctx)
+ mdata := make(map[string]any)
+ mdata["org_id"] = org.ID
+ err = models.RecordAuditLog(
+ ctx,
+ int(user.ID),
+ c.RealIP(),
+ models.LOG_ORG_FOLLOW,
+ fmt.Sprintf("Followed organization '%s' (%d)", org.Slug, org.ID),
+ mdata,
+ )
+ if err != nil {
+ return nil, err
+ }
}
payload := &model.FollowPayload{
@@ -3754,6 +4121,22 @@ func (r *mutationResolver) Unfollow(ctx context.Context, orgSlug string) (*model
if err != nil {
return nil, err
}
+
+ c := server.EchoForContext(ctx)
+ mdata := make(map[string]any)
+ mdata["org_id"] = org.ID
+ err = models.RecordAuditLog(
+ ctx,
+ int(user.ID),
+ c.RealIP(),
+ models.LOG_ORG_UNFOLLOW,
+ fmt.Sprintf("Unfollowed organization '%s' (%d)", org.Slug, org.ID),
+ mdata,
+ )
+ if err != nil {
+ return nil, err
+ }
+
payload := &model.FollowPayload{
Success: true,
Message: lt.Translate("User unfollows %s", orgSlug),
diff --git a/models/audit_log.go b/models/audit_log.go
new file mode 100644
index 0000000..5e5f268
--- /dev/null
+++ b/models/audit_log.go
@@ -0,0 +1,65 @@
+package models
+
+import (
+ "context"
+
+ auditlog "netlandish.com/x/gobwebs-auditlog"
+)
+
+// Event types for audit log entries
+const (
+ LOG_ACCT_LOGIN = "account_login"
+ LOG_ACCT_EMAIL_UPDATE = "account_email_update"
+ LOG_ACCT_EMAIL_CONF = "account_email_confirmation"
+ LOG_ACCT_PASSWORD_CHANGE = "account_password_change"
+ LOG_ACCT_PASSWORD_RESET = "account_password_reset"
+
+ LOG_PROFILE_UPDATED = "profile_updated"
+
+ LOG_ORG_ADDED = "organization_added"
+ LOG_ORG_UPDATED = "organization_updated"
+
+ LOG_BOOKMARK_ADDED = "bookmark_added"
+ LOG_BOOKMARK_UPDATED = "bookmark_added"
+ LOG_BOOKMARK_DELETED = "bookmark_added"
+
+ LOG_NOTE_ADDED = "note_added"
+ LOG_NOTE_UPDATED = "note_added"
+ LOG_NOTE_DELETED = "note_added"
+
+ LOG_SHORT_ADDED = "short_added"
+ LOG_SHORT_UPDATED = "short_updated"
+ LOG_SHORT_DELETED = "short_deleted"
+
+ LOG_LIST_ADDED = "list_added"
+ LOG_LIST_UPDATED = "list_updated"
+ LOG_LIST_DELETED = "list_deleted"
+
+ LOG_LIST_LINK_ADDED = "list_link_added"
+ LOG_LIST_LINK_UPDATED = "list_link_updated"
+ LOG_LIST_LINK_DELETED = "list_link_deleted"
+
+ LOG_MEMBER_ADDED = "member_added"
+ LOG_MEMBER_CONFIRMED = "member_confirmed"
+ LOG_MEMBER_REMOVED = "member_removed"
+
+ LOG_DOMAIN_ADDED = "domain_added"
+ LOG_DOMAIN_DELETED = "domain_deleted"
+
+ LOG_QRCODE_ADDED = "qrcode_added"
+ LOG_QRCODE_DELETED = "qrcode_deleted"
+
+ LOG_ORG_FOLLOW = "org_follow"
+ LOG_ORG_UNFOLLOW = "org_unfollow"
+)
+
+func RecordAuditLog(ctx context.Context, userID int,
+ ipAddress, eventType, details string, metadata map[string]any) error {
+ alog := auditlog.New(userID, ipAddress, eventType, details)
+ if metadata != nil {
+ for k, v := range metadata {
+ alog.AddMetadata(k, v)
+ }
+ }
+ return alog.Store(ctx)
+}
diff --git a/models/models.go b/models/models.go
index 5dff73d..6728306 100644
--- a/models/models.go
+++ b/models/models.go
@@ -19,11 +19,6 @@ const (
PRIVATERSSFEEDCONF = 300
)
-// Event types for audit log entries
-const (
- LOG_SHORT_ADDED = "short_added"
-)
-
type AnalyticsData map[string]int
// User ...
--
2.47.2