Class: Legion::Identity::Middleware

Inherits:
Object
  • Object
show all
Defined in:
lib/legion/identity/middleware.rb

Constant Summary collapse

SKIP_PATHS =
%w[/api/health /api/ready /api/openapi.json /metrics].freeze
LOOPBACK_BINDS =
%w[127.0.0.1 ::1 localhost].freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app, require_auth: false) ⇒ Middleware

Returns a new instance of Middleware.



9
10
11
12
# File 'lib/legion/identity/middleware.rb', line 9

def initialize(app, require_auth: false)
  @app          = app
  @require_auth = require_auth
end

Class Method Details

.require_auth?(bind:, mode:) ⇒ Boolean

Returns whether the API should require authentication. Skips auth for lite mode and loopback binds (local dev / CI).

Returns:

  • (Boolean)


60
61
62
63
64
65
# File 'lib/legion/identity/middleware.rb', line 60

def self.require_auth?(bind:, mode:)
  return false if mode == :lite
  return false if LOOPBACK_BINDS.include?(bind)

  true
end

Instance Method Details

#call(env) ⇒ Object



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/legion/identity/middleware.rb', line 14

def call(env)
  return @app.call(env) if skip_path?(env['PATH_INFO'])

  # Bridge from existing auth middleware
  auth_claims  = env['legion.auth']
  auth_method  = env['legion.auth_method']

  request = if auth_claims
              build_request(auth_claims, auth_method)
            elsif @require_auth
              # Auth middleware already handled 401 for protected paths;
              # this is a safety net for any path that slipped through.
              nil
            else
              # No auth required (loopback bind, lite mode, etc.).
              # Set a system-level principal so audit trails always have an identity.
              system_principal
            end

  env['legion.principal'] = request

  # Bridge to RBAC principal if legion-rbac is loaded.
  # This is a data bridge — set regardless of enforce/audit mode so
  # the RBAC middleware always has a typed principal to evaluate.
  # Guard: require Legion::Rbac.enabled? to confirm the real gem is loaded
  # (not a minimal test stub), and rescue construction errors defensively.
  if request && defined?(Legion::Rbac::Principal) &&
     defined?(Legion::Rbac) && Legion::Rbac.respond_to?(:enabled?) &&
     Legion::Rbac.enabled?
    begin
      env['legion.rbac_principal'] = Legion::Rbac::Principal.new(
        id:    request.principal_id,
        type:  request.kind == :service ? :worker : request.kind,
        roles: request.roles,
        team:  request.&.dig(:team)
      )
    rescue StandardError
      # Best-effort bridge: leave legion.rbac_principal unset on construction errors.
    end
  end

  @app.call(env)
end