Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 76 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package main

import (
"database/sql"
"embed"
"errors"
"fmt"
"io/fs"
"log"

Expand Down Expand Up @@ -41,6 +44,75 @@ func main() {
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
Setup(se.App)

se.Router.POST("/api/{type}/{id}/toggle-like", func(e *core.RequestEvent) error {
requestCollection := e.Request.PathValue("type")
targetId := e.Request.PathValue("id") // 被点赞物体的 ID
userId := e.Auth.Id

if requestCollection != "cps" && requestCollection != "characters" {
return e.Error(400, "Invalid request data", map[string]validation.Error{
"type": validation.NewError("validation_collection_type", "Invalid collection type."),
})
}

collection, err := app.FindCollectionByNameOrId("likes")
Comment thread
BretRen marked this conversation as resolved.
if err != nil {
return e.InternalServerError("Internal Server Error", map[string]any{"message": "Cannot find collection", "err": err})
}

existingRecord, err := app.FindFirstRecordByFilter("likes",
fmt.Sprintf("user = '%s' && target_id = '%s'", userId, targetId),
)
Comment thread
BretRen marked this conversation as resolved.
Comment on lines +63 to +65
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate and examine main.go to understand the context around lines 63-65
fd -t f "main.go" | head -5

Repository: pdnode-team/cp

Length of output: 65


🏁 Script executed:

# Get context around the flagged lines to understand the full implementation
head -120 main.go | tail -70

Repository: pdnode-team/cp

Length of output: 1909


🏁 Script executed:

# Check if there's a schema or migration file that defines the likes collection structure
fd -t f "migrations" | grep -E "\.(go|sql)$" | head -10

Repository: pdnode-team/cp

Length of output: 40


🏁 Script executed:

# Search for "likes" collection references to understand its schema
rg "likes" -A 3 -B 1 | head -50

Repository: pdnode-team/cp

Length of output: 2874


🏁 Script executed:

# Search for other uses of FindFirstRecordByFilter to understand the API
rg "FindFirstRecordByFilter" -B 2 -A 2

Repository: pdnode-team/cp

Length of output: 241


🏁 Script executed:

# Search for dbx.Params usage to see if this pattern is used elsewhere
rg "dbx\.Params" -B 2 -A 2

Repository: pdnode-team/cp

Length of output: 40


🏁 Script executed:

# Check imports to understand what dbx package is
head -30 main.go | grep -E "^import|dbx"

Repository: pdnode-team/cp

Length of output: 95


🏁 Script executed:

# Search for how filters are used elsewhere in the codebase
rg "FindFirstRecordByFilter\|FindRecordById\|GetList" -A 3 | head -40

Repository: pdnode-team/cp

Length of output: 40


🏁 Script executed:

# Look at PocketBase examples or tests to understand the filter API
rg "fmt\.Sprintf.*&&\|fmt\.Sprintf.*\|\|" -B 1 -A 1 | head -30

Repository: pdnode-team/cp

Length of output: 40


🏁 Script executed:

# Check if there's any documentation or comments about the filter syntax
rg "filter|Filter" --type go -B 1 -A 1 | grep -E "(//|/\*)" | head -20

Repository: pdnode-team/cp

Length of output: 40


🏁 Script executed:

# Check if there are other filter usages in main.go or if dbx is imported
rg "dbx" main.go

Repository: pdnode-team/cp

Length of output: 235


🏁 Script executed:

# Look for all filter strings to understand the pattern
rg "\&\&" main.go

Repository: pdnode-team/cp

Length of output: 287


🏁 Script executed:

# Check PocketBase documentation via web search or examine if there are type definitions
grep -r "FindFirstRecordByFilter" . 2>/dev/null | head -5

Repository: pdnode-team/cp

Length of output: 130


🏁 Script executed:

# Search for how dbx is actually used in query patterns
rg "Where\(dbx" -B 2 -A 3

Repository: pdnode-team/cp

Length of output: 263


🏁 Script executed:

# Check if there's a go.mod to understand PocketBase version
cat go.mod | grep pocketbase

Repository: pdnode-team/cp

Length of output: 134


🏁 Script executed:

# Search for the actual signature of FindFirstRecordByFilter or related methods
rg "func.*FindFirstRecord|func.*GetOne|func.*FindRecord" --type go -A 2

Repository: pdnode-team/cp

Length of output: 40


🏁 Script executed:

# Look for any imports or examples that show how to use filters safely with PocketBase
rg "RecordFilter|Filter\(" main.go -B 2 -A 2

Repository: pdnode-team/cp

Length of output: 201


🏁 Script executed:

# Check if there's usage of filter with parameters elsewhere
rg "filter.*:|\{:.*\}" -B 1 -A 1

Repository: pdnode-team/cp

Length of output: 3638


🏁 Script executed:

# Check PocketBase filter documentation by looking for examples in the codebase
rg "FindFirstRecordByFilter|FindRecordsByFilter" -B 5 -A 5 | head -60

Repository: pdnode-team/cp

Length of output: 551


🏁 Script executed:

# Look for how PocketBase filters are documented in comments or test files
find . -name "*.go" -type f -exec grep -l "filter" {} \; | xargs grep -l "user\|target" | head -3

Repository: pdnode-team/cp

Length of output: 40


🏁 Script executed:

# Search web documentation for PocketBase FindFirstRecordByFilter API
# to verify if it supports parameterized queries

Repository: pdnode-team/cp

Length of output: 40


🌐 Web query:

PocketBase FindFirstRecordByFilter parameterized queries dbx syntax v0.36

💡 Result:

In PocketBase v0.36 (latest as of 2026-04-21, with releases up to v0.36.8), the FindFirstRecordByFilter method supports parameterized queries using dbx syntax for safe binding of untrusted user input. Syntax (JSVM/Go): app.FindFirstRecordByFilter("posts", "slug = {:slug} && status = 'public'", dbx.Params{"slug": "test"}) - First argument: collection name or model. - Second: filter string with {:paramName} placeholders. - Last (variadic): dbx.Params{key: value} map(s) for binding parameters, preventing SQL injection. This works in both Go backend code and JSVM hooks/routes. Returns sql.ErrNoRows if no match. No breaking changes in v0.36 affecting this; query optimizations were added but syntax remains the same. Alternative for complex queries: use app.RecordQuery(collection).AndWhere(dbx.HashExp{"field": value}) or dbx.NewExp. Example: record := core.NewRecord(collection) $app.dao.recordQuery("posts") .andWhere(dbx.NewExp("slug = {:slug}", dbx.Params{"slug": slug})) .limit(1) .one(record)

Citations:


Add target_collection to the filter to prevent targeting the wrong like record.

The query at line 64 omits target_collection, allowing a CP and character with the same ID to be confused. A user who likes CP with id="X" will have that like deleted instead when attempting to like a character with id="X". Since target_collection is part of the unique index, it must be included in the lookup. Additionally, use parameterized queries instead of string interpolation for targetId from the URL path.

🐛 Proposed fix
 			existingRecord, err := app.FindFirstRecordByFilter("likes",
-				fmt.Sprintf("user = '%s' && target_id = '%s'", userId, targetId),
+				"user = {:userId} && target_id = {:targetId} && target_collection = {:targetCollection}",
+				dbx.Params{
+					"userId":           userId,
+					"targetId":         targetId,
+					"targetCollection": requestCollection,
+				},
 			)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@main.go` around lines 63 - 65, The filter used in app.FindFirstRecordByFilter
for the "likes" collection omits target_collection and interpolates targetId
directly, which can match the wrong like (e.g., CP vs character) and is unsafe;
update the filter passed to FindFirstRecordByFilter to include
"target_collection = '<collectionName>'" along with user and target_id (matching
the unique index) and switch to a parameterized lookup API or binding mechanism
to pass targetId (and collectionName) as parameters rather than using
fmt.Sprintf string interpolation; locate the call to app.FindFirstRecordByFilter
(variables userId, targetId, collection "likes") and modify it to include
target_collection and use the app/client method that accepts parameters or
placeholders.


if err != nil {
if !errors.Is(err, sql.ErrNoRows) {
return e.InternalServerError("Internal Server Error", map[string]any{
"message": "Cannot find record",
"err": err,
})
}
}

if existingRecord != nil {

err = app.Delete(existingRecord)
if err != nil {
return e.InternalServerError("Internal Server Error", map[string]any{
"message": "Cannot delete record",
"err": err,
})
}

return e.JSON(200, map[string]any{
"message": "Success",
"like": false,
"data": existingRecord,
})
}

record := core.NewRecord(collection)

record.Set("target_id", targetId)
record.Set("target_collection", requestCollection)
record.Set("user", e.Auth.Id)

err = app.Save(record)
Comment on lines +52 to +99
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find and examine the main.go file
find . -name "main.go" -type f | head -5

Repository: pdnode-team/cp

Length of output: 67


🏁 Script executed:

# Get the file structure to understand the repository
git ls-files | head -20

Repository: pdnode-team/cp

Length of output: 623


🏁 Script executed:

# Read the relevant section of main.go
cat -n main.go | sed -n '50,105p'

Repository: pdnode-team/cp

Length of output: 2016


🏁 Script executed:

# Search for FindRecordById and FindFirstRecordByFilter method definitions
rg "FindRecordById|FindFirstRecordByFilter" -A 5

Repository: pdnode-team/cp

Length of output: 303


🏁 Script executed:

# Search for FindRecordById method definition
rg "func.*FindRecordById" -A 3

Repository: pdnode-team/cp

Length of output: 40


🏁 Script executed:

# Also check if there's a method to get records by ID
rg "func.*\(.*\).*FindRecord" | head -20

Repository: pdnode-team/cp

Length of output: 40


🏁 Script executed:

# Check if this is a PocketBase application
rg "pocketbase" -i

Repository: pdnode-team/cp

Length of output: 5683


🏁 Script executed:

# Search for method definitions on app variable
rg "app\.\w+\(" main.go | head -20

Repository: pdnode-team/cp

Length of output: 803


🏁 Script executed:

# Look at imports to understand the framework
head -30 main.go

Repository: pdnode-team/cp

Length of output: 627


🏁 Script executed:

# Get more context of the endpoint handler
cat -n main.go | sed -n '35,110p'

Repository: pdnode-team/cp

Length of output: 2820


🏁 Script executed:

# Check if FindRecordById is used anywhere in the codebase
rg "FindRecordById|GetRecordById"

Repository: pdnode-team/cp

Length of output: 40


🏁 Script executed:

# Check the go.mod to see the exact PocketBase version
cat go.mod

Repository: pdnode-team/cp

Length of output: 1607


🏁 Script executed:

# Search for how records are fetched in the codebase - look at other patterns
rg "app\.\w+Record" main.go config.go

Repository: pdnode-team/cp

Length of output: 541


🏁 Script executed:

# Check PocketBase official documentation for available methods
# Let's search for any other record-fetching patterns in the codebase
rg "app\." main.go config.go | grep -i record

Repository: pdnode-team/cp

Length of output: 620


🏁 Script executed:

# Let's check config.go to see how records are typically accessed
cat config.go

Repository: pdnode-team/cp

Length of output: 3959


🏁 Script executed:

# Search for any method that gets a record by ID in the entire codebase
rg "Record\(" | head -20

Repository: pdnode-team/cp

Length of output: 105


🏁 Script executed:

# Check if there are any examples of fetching records in migrations or elsewhere
rg "FindRecord|GetRecord" --type go

Repository: pdnode-team/cp

Length of output: 40


🌐 Web query:

PocketBase 0.36.9 Go SDK FindRecordById method

💡 Result:

There is no official Go SDK for PocketBase client-side API interactions (only JS and Dart SDKs are official). Community-maintained Go SDKs exist, such as github.com/habibrosyad/pocketbase-go-sdk, but they do not have a method named FindRecordById. PocketBase core (github.com/pocketbase/pocketbase v0.36.9) has app.FindRecordById(collectionName, recordId) *core.Record, used server-side when extending PocketBase with Go. This method retrieves a single record by ID from the specified collection, returning nil and sql.ErrNoRows if not found. Example usage (server-side): record, err := app.FindRecordById("articles", "RECORD_ID") if err != nil { // handle error (e.g., sql.ErrNoRows if not found) } In community client SDKs like habibrosyad/pocketbase-go-sdk, use client.One(collectionName, recordId) map[string]any to fetch a single record by ID as a raw map, or client.OneTo(collectionName, recordId, &target) to unmarshal into a struct. Example (client-side with community SDK): record, err := client.One("posts_public", "record_id") if err != nil { log.Fatal(err) } log.Print(record["field"])

Citations:


🏁 Script executed:

# Let's also check if there's a Find method with different patterns
rg "func.*Find.*Record" --type go | head -20

Repository: pdnode-team/cp

Length of output: 40


🏁 Script executed:

# Verify e.BadRequestError method exists in the codebase or is available in pocketbase
rg "BadRequestError|e\.Error" main.go | head -10

Repository: pdnode-team/cp

Length of output: 288


Validate that the liked target actually exists.

The endpoint validates only the collection name, then saves arbitrary target_id values without checking if the target record exists. This allows creating likes for nonexistent records.

🛡️ Proposed validation
 			if requestCollection != "cps" && requestCollection != "characters" {
 				return e.Error(400, "Invalid request data", map[string]validation.Error{
 					"type": validation.NewError("validation_collection_type", "Invalid collection type."),
 				})
 			}
+
+			if _, err := app.FindRecordById(requestCollection, targetId); err != nil {
+				if errors.Is(err, sql.ErrNoRows) {
+					return e.BadRequestError("Invalid request data", map[string]validation.Error{
+						"id": validation.NewError("validation_target_not_found", "Target record does not exist."),
+					})
+				}
+				return e.InternalServerError("Internal Server Error", nil)
+			}
 
 			collection, err := app.FindCollectionByNameOrId("likes")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@main.go` around lines 52 - 99, Validate that the referenced target exists
before creating a like: after verifying requestCollection and before building
record := core.NewRecord(collection), query the target collection (use
app.FindCollectionByNameOrId or app.FindFirstRecordByFilter with
requestCollection and targetId) to ensure a record with that ID exists; if the
query returns sql.ErrNoRows return a 400 validation error (similar shape to the
existing validation.Error for "type"), and if the query returns any other error
return an InternalServerError; only proceed to call core.NewRecord and app.Save
when the target record is confirmed present.


if err != nil {
return e.InternalServerError("Internal Server Error", map[string]any{
"message": "Cannot save record",
"err": err,
})
}

return e.JSON(200, map[string]any{
"message": "Success",
"like": true,
"data": record,
})

}).Bind(apis.RequireAuth())

publicFS, err := fs.Sub(embeddedFiles, "pb_public")
if err != nil {
return err
Expand Down Expand Up @@ -82,7 +154,7 @@ func validateIdImmutable(e *core.RecordRequestEvent) error {

// 检查用户有没有传入id
if e.Record.Id != "" {
return apis.NewBadRequestError("Invalid request data", map[string]validation.Error{
return e.BadRequestError("Invalid request data", map[string]validation.Error{
"id": validation.NewError("validation_id_immutable", "Custom record IDs are not allowed."),
})
}
Expand All @@ -94,7 +166,7 @@ func validateIdImmutable(e *core.RecordRequestEvent) error {
func validateCharactersOwnership(e *core.RecordRequestEvent) error {

if e.Auth == nil {
return apis.NewUnauthorizedError("Unauthorized", nil)
return e.UnauthorizedError("Unauthorized", nil)
}

// Superuser 绕过
Expand Down Expand Up @@ -125,12 +197,12 @@ func validateCharactersOwnership(e *core.RecordRequestEvent) error {
Row(&count)

if err != nil {
return apis.NewInternalServerError("Database query failed", err)
return e.InternalServerError("Database query failed", err)
}

// 如果发现有角色不属于该用户,拦截请求
if count > 0 {
return apis.NewBadRequestError("Illegal association", map[string]validation.Error{
return e.Error(403, "Illegal association", map[string]validation.Error{
"characters": validation.NewError("invalid_character_owner", "One or more selected characters do not belong to you."),
})
}
Expand Down
118 changes: 118 additions & 0 deletions migrations/1776715443_created_likes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
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": null,
"deleteRule": null,
"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"
},
{
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "relation2375276105",
"maxSelect": 1,
"minSelect": 0,
"name": "user",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text361630566",
"max": 0,
"min": 0,
"name": "target_id",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "select3466706339",
"maxSelect": 1,
"name": "target_collection",
"presentable": false,
"required": false,
Comment thread
BretRen marked this conversation as resolved.
"system": false,
"type": "select",
"values": [
"cps",
"ccharacters"
]
},
{
"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_2190274710",
"indexes": [
"CREATE UNIQUE INDEX ` + "`" + `idx_unique_user_like` + "`" + ` ON ` + "`" + `likes` + "`" + ` (\n ` + "`" + `user` + "`" + `,\n ` + "`" + `target_id` + "`" + `,\n ` + "`" + `target_collection` + "`" + `\n)"
],
"listRule": null,
"name": "likes",
"system": false,
"type": "base",
"updateRule": null,
"viewRule": null
}`

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_2190274710")
if err != nil {
return err
}

return app.Delete(collection)
})
}
45 changes: 45 additions & 0 deletions migrations/1776715655_updated_likes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
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 {
collection, err := app.FindCollectionByNameOrId("pbc_2190274710")
if err != nil {
return err
}

// update collection data
if err := json.Unmarshal([]byte(`{
"indexes": [
"CREATE UNIQUE INDEX ` + "`" + `idx_unique_user_like` + "`" + ` ON ` + "`" + `likes` + "`" + ` (\n ` + "`" + `user` + "`" + `,\n ` + "`" + `target_id` + "`" + `,\n ` + "`" + `target_collection` + "`" + `\n)",
"CREATE INDEX ` + "`" + `idx_target_lookup` + "`" + ` ON ` + "`" + `likes` + "`" + ` (\n ` + "`" + `target_id` + "`" + `,\n ` + "`" + `target_collection` + "`" + `\n)"
]
}`), &collection); err != nil {
return err
}

return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("pbc_2190274710")
if err != nil {
return err
}

// update collection data
if err := json.Unmarshal([]byte(`{
"indexes": [
"CREATE UNIQUE INDEX ` + "`" + `idx_unique_user_like` + "`" + ` ON ` + "`" + `likes` + "`" + ` (\n ` + "`" + `user` + "`" + `,\n ` + "`" + `target_id` + "`" + `,\n ` + "`" + `target_collection` + "`" + `\n)"
]
}`), &collection); err != nil {
return err
}

return app.Save(collection)
})
}
60 changes: 60 additions & 0 deletions migrations/1776715948_updated_likes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package migrations

import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)

func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("pbc_2190274710")
if err != nil {
return err
}

// update field
if err := collection.Fields.AddMarshaledJSONAt(3, []byte(`{
"hidden": false,
"id": "select3466706339",
"maxSelect": 1,
"name": "target_collection",
"presentable": false,
"required": false,
"system": false,
"type": "select",
"values": [
"cps",
"characters"
]
}`)); err != nil {
return err
}

return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("pbc_2190274710")
if err != nil {
return err
}

// update field
if err := collection.Fields.AddMarshaledJSONAt(3, []byte(`{
"hidden": false,
"id": "select3466706339",
"maxSelect": 1,
"name": "target_collection",
"presentable": false,
"required": false,
"system": false,
"type": "select",
"values": [
"cps",
"ccharacters"
]
}`)); err != nil {
return err
}

return app.Save(collection)
})
}
42 changes: 42 additions & 0 deletions migrations/1776728364_updated_likes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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 {
collection, err := app.FindCollectionByNameOrId("pbc_2190274710")
if err != nil {
return err
}

// update collection data
if err := json.Unmarshal([]byte(`{
"listRule": "",
"viewRule": ""
}`), &collection); err != nil {
Comment on lines +18 to +21
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd /tmp && find . -name "*migrations*" -type d 2>/dev/null | head -5

Repository: pdnode-team/cp

Length of output: 40


🏁 Script executed:

# Check if the likes migration file exists
find . -name "1776728364_updated_likes.go" 2>/dev/null

Repository: pdnode-team/cp

Length of output: 98


🏁 Script executed:

# Also search for the file more broadly
find . -name "*likes*" -type f 2>/dev/null | grep -E "\.(go|json)$"

Repository: pdnode-team/cp

Length of output: 221


🏁 Script executed:

# Check the repo structure
git ls-files | head -20

Repository: pdnode-team/cp

Length of output: 623


🏁 Script executed:

# Read the full migration file to understand the context
cat -n ./migrations/1776728364_updated_likes.go

Repository: pdnode-team/cp

Length of output: 1165


🏁 Script executed:

# Also check the initial creation of likes to see if access was always public
cat -n ./migrations/1776715443_created_likes.go

Repository: pdnode-team/cp

Length of output: 3469


🏁 Script executed:

# Check other migration files to see the pattern for collections
cat -n ./migrations/1776131344_created_cps.go | head -40

Repository: pdnode-team/cp

Length of output: 1250


🏁 Script executed:

# Check if there's documentation about access rules
find . -name "*.md" -o -name "*.txt" | xargs grep -l "likes\|viewRule\|listRule" 2>/dev/null

Repository: pdnode-team/cp

Length of output: 42


🏁 Script executed:

# Look for any schema documentation or comments in main config
cat -n ./config.go | head -50

Repository: pdnode-team/cp

Length of output: 1335


🏁 Script executed:

# Check other migrations to see access patterns in other collections
rg "listRule\|viewRule" migrations/ -A 2 -B 2 | head -80

Repository: pdnode-team/cp

Length of output: 40


🏁 Script executed:

# Look for any explicit justification or comments about public likes
git log --oneline migrations/1776728364_updated_likes.go 2>/dev/null | head -5

Repository: pdnode-team/cp

Length of output: 88


🏁 Script executed:

# Check if there are any other recent migrations to understand intent
ls -la migrations/ | tail -20

Repository: pdnode-team/cp

Length of output: 1581


🏁 Script executed:

# Check the intermediate migrations to see the progression of access rules
cat -n ./migrations/1776715655_updated_likes.go

Repository: pdnode-team/cp

Length of output: 1755


🏁 Script executed:

# Check the next intermediate migration
cat -n ./migrations/1776715948_updated_likes.go

Repository: pdnode-team/cp

Length of output: 1672


🏁 Script executed:

# Check the users migration to see if there's documentation about why viewRule was made public
cat -n ./migrations/1776571426_updated_users.go

Repository: pdnode-team/cp

Length of output: 1132


🏁 Script executed:

# Check if there's a frontend or documentation explaining public likes access
find . -name "*.js" -o -name "*.ts" -o -name "*.md" | xargs grep -l "likes" 2>/dev/null | head -5

Repository: pdnode-team/cp

Length of output: 40


🏁 Script executed:

# Check git history to see if there's a commit message explaining the change to public likes
git log --all --oneline -- migrations/1776728364_updated_likes.go 2>/dev/null

Repository: pdnode-team/cp

Length of output: 88


🏁 Script executed:

# Check the full git log for the likes migrations to understand the progression
git log --all --oneline -- migrations/*likes* 2>/dev/null

Repository: pdnode-team/cp

Length of output: 88


🏁 Script executed:

# Look for any README or documentation about the app's architecture
cat -n ./README.md 2>/dev/null | head -50

Repository: pdnode-team/cp

Length of output: 40


Restrict likes to authenticated owner only.

Setting both listRule and viewRule to empty strings exposes all likes records—including the mappings between users and their liked targets—to unauthenticated clients. Unlike the public user directory, exposure of individual user preferences poses a privacy concern. If the UI only requires displaying the current user's likes, restrict reads to authenticated owner access.

🔒 Proposed rule tightening
-			"listRule": "",
-			"viewRule": ""
+			"listRule": "@request.auth.id != \"\" && user = `@request.auth.id`",
+			"viewRule": "@request.auth.id != \"\" && user = `@request.auth.id`"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if err := json.Unmarshal([]byte(`{
"listRule": "",
"viewRule": ""
}`), &collection); err != nil {
if err := json.Unmarshal([]byte(`{
"listRule": "@request.auth.id != \"\" && user = `@request.auth.id`",
"viewRule": "@request.auth.id != \"\" && user = `@request.auth.id`"
}`), &collection); err != nil {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@migrations/1776728364_updated_likes.go` around lines 18 - 21, The current
json.Unmarshal that sets collection.listRule and collection.viewRule to empty
strings leaves likes public; update that JSON so the "listRule" and "viewRule"
enforce authenticated-owner access (e.g., require request.auth.id equals the
like owner field) instead of empty strings. Locate the json.Unmarshal(...)
assignment to the collection variable in migrations/1776728364_updated_likes.go
and replace the empty string values for "listRule" and "viewRule" with rules
that check for an authenticated user and match the like record's owner id (use
the same owner field name used elsewhere in the likes collection).

return err
}

return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("pbc_2190274710")
if err != nil {
return err
}

// update collection data
if err := json.Unmarshal([]byte(`{
"listRule": null,
"viewRule": null
}`), &collection); err != nil {
return err
}

return app.Save(collection)
})
}
Loading