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
20 changes: 20 additions & 0 deletions src/relay/src/_index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,23 @@ entries:
- time
- security
method: run

# wippy.relay:central_test
- name: central_test
kind: function.lua
meta:
name: Relay Central Identity Test
type: test
suite: relay
comment: Tests user identity and scope propagation from websocket metadata.
group: Relay
tags:
- relay
- identity
- tests
source: file://central_test.lua
imports:
central: wippy.relay:central
test: wippy.test:test
modules: []
method: run
31 changes: 23 additions & 8 deletions src/relay/src/central.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ local consts = require("consts")
local plugin_discovery = require("plugin_discovery")

local logger = require("logger"):named("relay")
local security_mod = security

type UserHubInfo = {
hub_pid: string,
Expand Down Expand Up @@ -39,19 +40,27 @@ local function table_length(t: any): number
return count
end

local function identity_from_metadata(config: ValidatedConfig, user_id: string, metadata: any): (any?, any?, any?, string?)
local user_metadata = type(metadata.user_metadata) == "table" and metadata.user_metadata or {}
local scope_id = type(metadata.scope_id) == "string" and metadata.scope_id ~= "" and metadata.scope_id or config.user_security_scope
local user_actor = security_mod.new_actor(user_id, user_metadata)

local user_scope, scope_err = security_mod.named_scope(tostring(scope_id))
if scope_err then
return nil, nil, nil, "Failed to get user security scope: " .. scope_err
end

return user_actor, user_scope, user_metadata, nil
end

local function get_or_create_user_hub(state: CentralState, user_id: string, metadata: any): string?
local hub = state.user_hubs[user_id]
if hub then
return hub.hub_pid
end

local user_metadata = metadata.user_metadata or {}
local user_actor = security.new_actor(user_id, user_metadata)

local user_scope, scope_err = security.named_scope(state.config.user_security_scope)
if scope_err then
error("Failed to get user security scope: " .. scope_err)
end
local user_actor, user_scope, user_metadata, identity_err = identity_from_metadata(state.config, user_id, metadata or {})
if identity_err then error(identity_err) end

local hub_pid, err = process.with_context({})
:with_scope(user_scope)
Expand Down Expand Up @@ -284,4 +293,10 @@ local function run(): any
return { status = "shutdown", hubs = state.total_hubs }
end

return { run = run }
return {
run = run,
_identity_from_metadata = identity_from_metadata,
_set_security_for_test = function(fake: any)
security_mod = fake or security
end,
}
72 changes: 72 additions & 0 deletions src/relay/src/central_test.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
local test = require("test")
local central = require("central")

local function fake_security(calls: any): any
return {
new_actor = function(user_id: string, meta: any)
calls.actor_id = user_id
calls.actor_meta = meta
return {
id = function() return user_id end,
meta = function() return meta end,
}
end,
named_scope = function(scope_id: string)
calls.scope_id = scope_id
return { id = scope_id }, nil
end,
}
end

local function config(scope_id: string)
return {
max_connections_per_user = 10,
user_hub_inactivity_timeout = "5m",
user_hub_host = "wippy.terminal:host",
user_security_scope = scope_id,
gc_check_interval = "1m",
heartbeat_interval = "30s",
message_queue_size = 100,
queue_multiplier = 2,
}
end

local function define_tests()
test.describe("relay central identity", function()
test.it("uses the authenticated scope id when relay metadata carries it", function()
local calls = {}
central._set_security_for_test(fake_security(calls))
local actor, scope, meta, err = central._identity_from_metadata(config("fallback:scope"), "admin@wippy.local", {
scope_id = "app.security:admin",
user_metadata = {
security_groups = { "app.security:admin", "app.security:user" },
email = "admin@wippy.local",
},
})

test.expect(err).to_be_nil()
test.expect(actor).not_to_be_nil()
test.expect(scope).not_to_be_nil()
test.expect(actor:id()).to_equal("admin@wippy.local")
test.expect(meta.security_groups[1]).to_equal("app.security:admin")
test.expect(calls.scope_id).to_equal("app.security:admin")
central._set_security_for_test(nil)
end)

test.it("falls back to the configured user scope for legacy relay metadata", function()
local calls = {}
central._set_security_for_test(fake_security(calls))
local actor, scope, meta, err = central._identity_from_metadata(config("app.security:user"), "member@wippy.local", {})

test.expect(err).to_be_nil()
test.expect(actor).not_to_be_nil()
test.expect(scope).not_to_be_nil()
test.expect(actor:id()).to_equal("member@wippy.local")
test.expect(type(meta)).to_equal("table")
test.expect(calls.scope_id).to_equal("app.security:user")
central._set_security_for_test(nil)
end)
end)
end

return { run = test.run_cases(define_tests) }
Loading