feat(jsmod): dynamic sandboxing for db access v1.3.7
This commit is contained in:
parent
2c22ce193b
commit
615b2b5338
106
js_export.go
106
js_export.go
@ -3,18 +3,55 @@ package db
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"apigo.cc/go/cast"
|
||||||
|
"apigo.cc/go/file"
|
||||||
"apigo.cc/go/jsmod"
|
"apigo.cc/go/jsmod"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
jsmod.Register("db", map[string]any{
|
jsmod.Register("db", map[string]any{
|
||||||
"get": func(ctx context.Context, name string) (*jsDB, error) {
|
"get": func(ctx context.Context, name string) (*jsDB, error) {
|
||||||
|
isReadOnly := false
|
||||||
|
|
||||||
|
// 安全模式下的动态判定
|
||||||
|
if jsmod.IsSafeMode(ctx) {
|
||||||
|
if strings.HasPrefix(name, "sqlite://") {
|
||||||
|
// 1. SQLite 路径校验
|
||||||
|
path := strings.TrimPrefix(name, "sqlite://")
|
||||||
|
if idx := strings.Index(path, "?"); idx != -1 {
|
||||||
|
path = path[:idx]
|
||||||
|
}
|
||||||
|
// 如果路径不在沙箱内,VerifyPathForSafeMode 会直接返回 error,阻止连接
|
||||||
|
_, err := file.VerifyPathForSafeMode(ctx, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
isReadOnly = false
|
||||||
|
} else {
|
||||||
|
// 2. 远程数据库前缀校验
|
||||||
|
var allowedDSNs []string
|
||||||
|
cast.Convert(&allowedDSNs, ctx.Value("AllowedDSNs"))
|
||||||
|
matched := false
|
||||||
|
for _, prefix := range allowedDSNs {
|
||||||
|
if strings.HasPrefix(name, prefix) {
|
||||||
|
matched = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !matched {
|
||||||
|
// 未命中白名单,降级为只读模式
|
||||||
|
isReadOnly = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
d := GetDB(name, nil)
|
d := GetDB(name, nil)
|
||||||
if d.Error != nil {
|
if d.Error != nil {
|
||||||
return nil, d.Error
|
return nil, d.Error
|
||||||
}
|
}
|
||||||
return &jsDB{db: d, ctx: ctx}, nil
|
return &jsDB{db: d, ctx: ctx, isReadOnly: isReadOnly}, nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -22,13 +59,14 @@ func init() {
|
|||||||
type jsDB struct {
|
type jsDB struct {
|
||||||
db *DB
|
db *DB
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
isReadOnly bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var errSafeMode = errors.New("database write operation is restricted in safe mode")
|
var errReadOnly = errors.New("database is in read-only mode for this context")
|
||||||
|
|
||||||
func (jd *jsDB) checkSafe() error {
|
func (jd *jsDB) checkWrite() error {
|
||||||
if jsmod.IsSafeMode(jd.ctx) {
|
if jd.isReadOnly {
|
||||||
return errSafeMode
|
return errReadOnly
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -40,59 +78,59 @@ func (jd *jsDB) Query(query string, args ...any) *QueryResult {
|
|||||||
|
|
||||||
// Write Operations
|
// Write Operations
|
||||||
func (jd *jsDB) Exec(query string, args ...any) *ExecResult {
|
func (jd *jsDB) Exec(query string, args ...any) *ExecResult {
|
||||||
if jd.checkSafe() != nil {
|
if err := jd.checkWrite(); err != nil {
|
||||||
return &ExecResult{Error: errSafeMode}
|
return &ExecResult{Error: err}
|
||||||
}
|
}
|
||||||
return jd.db.Exec(query, args...)
|
return jd.db.Exec(query, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jd *jsDB) Insert(table string, data any) *ExecResult {
|
func (jd *jsDB) Insert(table string, data any) *ExecResult {
|
||||||
if jd.checkSafe() != nil {
|
if err := jd.checkWrite(); err != nil {
|
||||||
return &ExecResult{Error: errSafeMode}
|
return &ExecResult{Error: err}
|
||||||
}
|
}
|
||||||
return jd.db.Insert(table, data)
|
return jd.db.Insert(table, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jd *jsDB) Update(table string, data any, conditions string, args ...any) *ExecResult {
|
func (jd *jsDB) Update(table string, data any, conditions string, args ...any) *ExecResult {
|
||||||
if jd.checkSafe() != nil {
|
if err := jd.checkWrite(); err != nil {
|
||||||
return &ExecResult{Error: errSafeMode}
|
return &ExecResult{Error: err}
|
||||||
}
|
}
|
||||||
return jd.db.Update(table, data, conditions, args...)
|
return jd.db.Update(table, data, conditions, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jd *jsDB) Delete(table string, conditions string, args ...any) *ExecResult {
|
func (jd *jsDB) Delete(table string, conditions string, args ...any) *ExecResult {
|
||||||
if jd.checkSafe() != nil {
|
if err := jd.checkWrite(); err != nil {
|
||||||
return &ExecResult{Error: errSafeMode}
|
return &ExecResult{Error: err}
|
||||||
}
|
}
|
||||||
return jd.db.Delete(table, conditions, args...)
|
return jd.db.Delete(table, conditions, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jd *jsDB) Replace(table string, data any) *ExecResult {
|
func (jd *jsDB) Replace(table string, data any) *ExecResult {
|
||||||
if jd.checkSafe() != nil {
|
if err := jd.checkWrite(); err != nil {
|
||||||
return &ExecResult{Error: errSafeMode}
|
return &ExecResult{Error: err}
|
||||||
}
|
}
|
||||||
return jd.db.Replace(table, data)
|
return jd.db.Replace(table, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transaction Support
|
// Transaction Support
|
||||||
func (jd *jsDB) Begin() (*jsTx, error) {
|
func (jd *jsDB) Begin() (*jsTx, error) {
|
||||||
// Note: We don't block Begin() itself, but destructive operations within Tx will be blocked
|
|
||||||
tx := jd.db.Begin()
|
tx := jd.db.Begin()
|
||||||
if tx.Error != nil {
|
if tx.Error != nil {
|
||||||
return nil, tx.Error
|
return nil, tx.Error
|
||||||
}
|
}
|
||||||
return &jsTx{tx: tx, ctx: jd.ctx}, nil
|
return &jsTx{tx: tx, ctx: jd.ctx, isReadOnly: jd.isReadOnly}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// jsTx wraps *Tx for JS environment
|
// jsTx wraps *Tx for JS environment
|
||||||
type jsTx struct {
|
type jsTx struct {
|
||||||
tx *Tx
|
tx *Tx
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
isReadOnly bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jt *jsTx) checkSafe() error {
|
func (jt *jsTx) checkWrite() error {
|
||||||
if jsmod.IsSafeMode(jt.ctx) {
|
if jt.isReadOnly {
|
||||||
return errSafeMode
|
return errReadOnly
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -102,41 +140,47 @@ func (jt *jsTx) Query(query string, args ...any) *QueryResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (jt *jsTx) Exec(query string, args ...any) *ExecResult {
|
func (jt *jsTx) Exec(query string, args ...any) *ExecResult {
|
||||||
if jt.checkSafe() != nil {
|
if err := jt.checkWrite(); err != nil {
|
||||||
return &ExecResult{Error: errSafeMode}
|
return &ExecResult{Error: err}
|
||||||
}
|
}
|
||||||
return jt.tx.Exec(query, args...)
|
return jt.tx.Exec(query, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jt *jsTx) Insert(table string, data any) *ExecResult {
|
func (jt *jsTx) Insert(table string, data any) *ExecResult {
|
||||||
if jt.checkSafe() != nil {
|
if err := jt.checkWrite(); err != nil {
|
||||||
return &ExecResult{Error: errSafeMode}
|
return &ExecResult{Error: err}
|
||||||
}
|
}
|
||||||
return jt.tx.Insert(table, data)
|
return jt.tx.Insert(table, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jt *jsTx) Update(table string, data any, conditions string, args ...any) *ExecResult {
|
func (jt *jsTx) Update(table string, data any, conditions string, args ...any) *ExecResult {
|
||||||
if jt.checkSafe() != nil {
|
if err := jt.checkWrite(); err != nil {
|
||||||
return &ExecResult{Error: errSafeMode}
|
return &ExecResult{Error: err}
|
||||||
}
|
}
|
||||||
return jt.tx.Update(table, data, conditions, args...)
|
return jt.tx.Update(table, data, conditions, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jt *jsTx) Delete(table string, conditions string, args ...any) *ExecResult {
|
func (jt *jsTx) Delete(table string, conditions string, args ...any) *ExecResult {
|
||||||
if jt.checkSafe() != nil {
|
if err := jt.checkWrite(); err != nil {
|
||||||
return &ExecResult{Error: errSafeMode}
|
return &ExecResult{Error: err}
|
||||||
}
|
}
|
||||||
return jt.tx.Delete(table, conditions, args...)
|
return jt.tx.Delete(table, conditions, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jt *jsTx) Replace(table string, data any) *ExecResult {
|
func (jt *jsTx) Replace(table string, data any) *ExecResult {
|
||||||
if jt.checkSafe() != nil {
|
if err := jt.checkWrite(); err != nil {
|
||||||
return &ExecResult{Error: errSafeMode}
|
return &ExecResult{Error: err}
|
||||||
}
|
}
|
||||||
return jt.tx.Replace(table, data)
|
return jt.tx.Replace(table, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jt *jsTx) Commit() error { return jt.tx.Commit() }
|
func (jt *jsTx) Commit() error {
|
||||||
|
if err := jt.checkWrite(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return jt.tx.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
func (jt *jsTx) Rollback() error { return jt.tx.Rollback() }
|
func (jt *jsTx) Rollback() error { return jt.tx.Rollback() }
|
||||||
|
|
||||||
// Metadata
|
// Metadata
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user