feat: enforce youth mode policy and improve search
This commit is contained in:
@@ -16,12 +16,17 @@ import (
|
||||
const (
|
||||
imReasonFriendAddDisabled = "FRIEND_ADD_DISABLED"
|
||||
imReasonMessageCurfew = "MESSAGE_CURFEW_ACTIVE"
|
||||
imReasonYouthRestricted = "YOUTH_MODE_RESTRICTED"
|
||||
|
||||
imYouthActionMessage = "message"
|
||||
imYouthActionCall = "call"
|
||||
)
|
||||
|
||||
type imPolicyDecision struct {
|
||||
Allowed bool
|
||||
Reason string
|
||||
Message string
|
||||
Details map[string]any
|
||||
}
|
||||
|
||||
func imAllowDecision() imPolicyDecision {
|
||||
@@ -32,6 +37,66 @@ func imDenyDecision(reason, message string) imPolicyDecision {
|
||||
return imPolicyDecision{Allowed: false, Reason: reason, Message: strings.TrimSpace(message)}
|
||||
}
|
||||
|
||||
func imDenyDecisionDetails(reason, message string, details map[string]any) imPolicyDecision {
|
||||
decision := imDenyDecision(reason, message)
|
||||
decision.Details = details
|
||||
return decision
|
||||
}
|
||||
|
||||
func imYouthModePolicyDecision(tenantID types.TenantID, actor types.Uid, action string, now time.Time) imPolicyDecision {
|
||||
if store.Security == nil || actor.IsZero() {
|
||||
return imAllowDecision()
|
||||
}
|
||||
settings, err := store.Security.GetYouthMode(tenantID, actor)
|
||||
if err != nil {
|
||||
if !errors.Is(err, types.ErrNotFound) && !errors.Is(err, types.ErrUnsupported) {
|
||||
logs.Warn.Printf("youth mode policy lookup failed: tenant=%d user=%s action=%s err=%v", tenantID, actor.UserId(), action, err)
|
||||
}
|
||||
return imAllowDecision()
|
||||
}
|
||||
if settings == nil || !settings.Enabled {
|
||||
return imAllowDecision()
|
||||
}
|
||||
switch strings.TrimSpace(action) {
|
||||
case imYouthActionCall:
|
||||
if youthModeHasRestriction(settings, "night_quiet") && imYouthNightQuietActive(now) {
|
||||
return imDenyDecisionDetails(imReasonYouthRestricted, "restricted by youth mode", map[string]any{
|
||||
"restriction": "night_quiet",
|
||||
"policy_version": settings.PolicyVersion,
|
||||
})
|
||||
}
|
||||
case imYouthActionMessage:
|
||||
// content_filter is enforced by routing messages through content moderation
|
||||
// before persistence; the decision remains allow unless moderation rejects it.
|
||||
return imAllowDecision()
|
||||
}
|
||||
return imAllowDecision()
|
||||
}
|
||||
|
||||
func youthModeHasRestriction(settings *types.YouthModeSettings, code string) bool {
|
||||
if settings == nil {
|
||||
return false
|
||||
}
|
||||
code = strings.TrimSpace(code)
|
||||
for _, item := range settings.Restrictions {
|
||||
if strings.TrimSpace(item) == code {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func imYouthNightQuietActive(now time.Time) bool {
|
||||
if now.IsZero() {
|
||||
now = time.Now()
|
||||
}
|
||||
local := now.In(time.Local)
|
||||
minute := local.Hour()*60 + local.Minute()
|
||||
start := 22 * 60
|
||||
end := 6 * 60
|
||||
return minute >= start || minute < end
|
||||
}
|
||||
|
||||
func imTenantCodeForPolicy(tenantID types.TenantID) (string, error) {
|
||||
tenant, err := store.Tenants.GetByID(tenantID)
|
||||
if err != nil || tenant == nil {
|
||||
@@ -313,10 +378,14 @@ func writeIMPolicyDecision(wrt http.ResponseWriter, req *http.Request, decision
|
||||
if reason == "" {
|
||||
reason = imReasonPermissionDenied
|
||||
}
|
||||
writeIMError(wrt, req, http.StatusForbidden, 30005, reason, message, map[string]any{
|
||||
details := map[string]any{
|
||||
"reason": reason,
|
||||
"message": message,
|
||||
})
|
||||
}
|
||||
for key, value := range decision.Details {
|
||||
details[key] = value
|
||||
}
|
||||
writeIMError(wrt, req, http.StatusForbidden, 30005, reason, message, details)
|
||||
}
|
||||
|
||||
func imCurfewPolicyReply(msg *ClientComMessage, decision imPolicyDecision) *ServerComMessage {
|
||||
@@ -328,3 +397,23 @@ func imCurfewPolicyReply(msg *ClientComMessage, decision imPolicyDecision) *Serv
|
||||
}
|
||||
return reply
|
||||
}
|
||||
|
||||
func imYouthPolicyReply(msg *ClientComMessage, decision imPolicyDecision) *ServerComMessage {
|
||||
reply := ErrPolicyReply(msg, types.TimeNow())
|
||||
reply.Ctrl.Text = "restricted by youth mode"
|
||||
params := map[string]any{
|
||||
"reason": imReasonYouthRestricted,
|
||||
"message": "restricted by youth mode",
|
||||
}
|
||||
if strings.TrimSpace(decision.Reason) != "" {
|
||||
params["reason"] = decision.Reason
|
||||
}
|
||||
if strings.TrimSpace(decision.Message) != "" {
|
||||
params["message"] = decision.Message
|
||||
}
|
||||
for key, value := range decision.Details {
|
||||
params[key] = value
|
||||
}
|
||||
reply.Ctrl.Params = params
|
||||
return reply
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user