141 lines
4.7 KiB
Text
141 lines
4.7 KiB
Text
|
|
# RBAC Policy Configuration
|
||
|
|
#
|
||
|
|
# Defines access rules for the website. Identity (who the user is)
|
||
|
|
# comes from JWT claims or session; this file defines what each group
|
||
|
|
# is allowed to access.
|
||
|
|
#
|
||
|
|
# Group names must match the role strings derived from JWT:
|
||
|
|
# Role::Admin → "admin"
|
||
|
|
# Role::Moderator → "moderator"
|
||
|
|
# Role::User → "user"
|
||
|
|
# Role::Guest → "guest"
|
||
|
|
# Role::Custom(s) → s
|
||
|
|
#
|
||
|
|
# Validate: nickel export rbac.ncl
|
||
|
|
|
||
|
|
let C = import "./nickel/rbac/contracts.ncl" in
|
||
|
|
|
||
|
|
# Reusable effect sets — compose with array concatenation (@)
|
||
|
|
let E = {
|
||
|
|
redirect_login = [{ type = "redirect", to = "/login?return={request.path}" }],
|
||
|
|
audit_deny = [{ type = "audit_log", event = "access_denied", level = "warn" }],
|
||
|
|
audit_allow = [{ type = "audit_log", event = "access_granted", level = "info" }],
|
||
|
|
api_forbidden = [{ type = "response", status = 403, body.error = "Forbidden" }],
|
||
|
|
log_warn = [{ type = "log", level = "warn", message = "Denied: {request.path}" }],
|
||
|
|
security_alert = [{
|
||
|
|
type = "notify",
|
||
|
|
channel = "security",
|
||
|
|
message = "Admin access: {request.path} by {user.email}",
|
||
|
|
}],
|
||
|
|
} in
|
||
|
|
|
||
|
|
{
|
||
|
|
groups = [
|
||
|
|
{
|
||
|
|
name = "admin",
|
||
|
|
parent_groups = [],
|
||
|
|
permissions = [
|
||
|
|
{
|
||
|
|
resource = "endpoint",
|
||
|
|
pattern = "/api/*",
|
||
|
|
outcome = "allow",
|
||
|
|
methods = [],
|
||
|
|
effects = E.audit_allow,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
resource = "page",
|
||
|
|
pattern = "/*",
|
||
|
|
outcome = "allow",
|
||
|
|
methods = [],
|
||
|
|
effects = [],
|
||
|
|
},
|
||
|
|
],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name = "moderator",
|
||
|
|
parent_groups = ["user"],
|
||
|
|
permissions = [
|
||
|
|
{
|
||
|
|
resource = "endpoint",
|
||
|
|
pattern = "/api/content/*",
|
||
|
|
outcome = "allow",
|
||
|
|
methods = ["GET", "POST", "PUT"],
|
||
|
|
effects = E.audit_allow,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
resource = "endpoint",
|
||
|
|
pattern = "/api/admin/*",
|
||
|
|
outcome = "deny",
|
||
|
|
methods = [],
|
||
|
|
effects = E.audit_deny @ E.api_forbidden,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
resource = "page",
|
||
|
|
pattern = "/admin/content/*",
|
||
|
|
outcome = "allow",
|
||
|
|
methods = [],
|
||
|
|
effects = [],
|
||
|
|
},
|
||
|
|
],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name = "user",
|
||
|
|
parent_groups = [],
|
||
|
|
permissions = [
|
||
|
|
{
|
||
|
|
resource = "page",
|
||
|
|
pattern = "/*",
|
||
|
|
outcome = "allow",
|
||
|
|
methods = [],
|
||
|
|
effects = [],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
resource = "endpoint",
|
||
|
|
pattern = "/api/*",
|
||
|
|
outcome = "deny",
|
||
|
|
methods = [],
|
||
|
|
effects = E.audit_deny @ E.api_forbidden,
|
||
|
|
},
|
||
|
|
],
|
||
|
|
},
|
||
|
|
],
|
||
|
|
|
||
|
|
user_overrides = [
|
||
|
|
# Example: block a specific user and redirect to login
|
||
|
|
# {
|
||
|
|
# id = "blocked@ontoref.dev",
|
||
|
|
# permissions = [{
|
||
|
|
# resource = "page",
|
||
|
|
# pattern = "/*",
|
||
|
|
# outcome = "deny",
|
||
|
|
# methods = [],
|
||
|
|
# effects = E.audit_deny @ E.security_alert @ E.redirect_login,
|
||
|
|
# }],
|
||
|
|
# },
|
||
|
|
],
|
||
|
|
|
||
|
|
defaults = {
|
||
|
|
unauthenticated_allow = [
|
||
|
|
# Protected paths evaluated first — first match wins
|
||
|
|
{ resource = "page", pattern = "/admin/*", outcome = "deny", methods = [], effects = E.audit_deny @ E.redirect_login },
|
||
|
|
# Public pages
|
||
|
|
{ resource = "page", pattern = "/login", outcome = "allow", methods = [], effects = [] },
|
||
|
|
{ resource = "page", pattern = "/register", outcome = "allow", methods = [], effects = [] },
|
||
|
|
{ resource = "page", pattern = "/*", outcome = "allow", methods = [], effects = [] },
|
||
|
|
# Public endpoints and assets
|
||
|
|
{ resource = "endpoint", pattern = "/api/health", outcome = "allow", methods = [], effects = [] },
|
||
|
|
{ resource = "endpoint", pattern = "/api/ws", outcome = "allow", methods = ["GET"], effects = [] },
|
||
|
|
{ resource = "endpoint", pattern = "/api/auth/*", outcome = "allow", methods = [], effects = [] },
|
||
|
|
{ resource = "endpoint", pattern = "/api/server_request_otp*", outcome = "allow", methods = ["POST"], effects = [] },
|
||
|
|
{ resource = "endpoint", pattern = "/api/server_verify_otp*", outcome = "allow", methods = ["POST"], effects = [] },
|
||
|
|
{ resource = "endpoint", pattern = "/api/content-render/*", outcome = "allow", methods = ["GET"], effects = [] },
|
||
|
|
{ resource = "endpoint", pattern = "/api/content-index/*", outcome = "allow", methods = ["GET"], effects = [] },
|
||
|
|
{ resource = "asset", pattern = "/*", outcome = "allow", methods = [], effects = [] },
|
||
|
|
],
|
||
|
|
unauthenticated_deny = [],
|
||
|
|
authenticated_fallback = "deny",
|
||
|
|
unauthenticated_fallback = "deny",
|
||
|
|
fallback_effects = E.log_warn,
|
||
|
|
},
|
||
|
|
} | C.FileRbacConfig
|