Class: Foobara::AWS::Authorizer

Inherits:
Object
  • Object
show all
Defined in:
lib/foobara/aws/authorizer.rb

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(issuer:, audience:, anonymous: [], mount: "/run", jwks_url: nil) ⇒ Authorizer

Returns a new instance of Authorizer.



68
69
70
71
72
73
74
75
76
# File 'lib/foobara/aws/authorizer.rb', line 68

def initialize(issuer:, audience:, anonymous: [], mount: "/run", jwks_url: nil)
  @issuer = issuer.to_s.chomp("/")
  @audience = Array(audience)
  # Foobara's own full command names — "Posts::ListPosts" — so the list the
  # packager derives from the manifest travels here untranslated.
  @anonymous = Array(anonymous).map(&:to_s)
  @mount = mount
  @jwks_url = jwks_url || "#{@issuer}/.well-known/jwks.json"
end

Class Method Details

.from_env(issuer:, audience:, jwks_url: nil) ⇒ Object

Reads the plan-derived settings Service puts in the environment, so a deployed authorizer needs no configuration of its own beyond who issued the tokens.



60
61
62
63
64
65
66
# File 'lib/foobara/aws/authorizer.rb', line 60

def self.from_env(issuer:, audience:, jwks_url: nil)
  new(
    issuer:, audience:, jwks_url:,
    anonymous: ENV.fetch("FOOBARA_ANONYMOUS", "").split(","),
    mount: ENV.fetch("FOOBARA_MOUNT", "/run")
  )
end

Instance Method Details

#call(event, _lambda_context = nil) ⇒ Object

SIMPLE response format:

{ "isAuthorized" => bool, "context" => { ...claims } }

Anything in context reaches the command Lambda at requestContext.authorizer.lambda, which is where the handler reads the viewer from.



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/foobara/aws/authorizer.rb', line 83

def call(event, _lambda_context = nil)
  token = bearer(event)
  command = command_name(event)

  if token.nil?
    # No token at all. Allowed only where the command is declared public —
    # and it reaches the command with no claims, so a public viewer-aware
    # command correctly sees an anonymous caller.
    return allow({}) if anonymous?(command)

    return deny
  end

  begin
    allow(claims_from(token))
  rescue Unverified
    # A present-but-invalid token: expired, wrong audience, bad signature.
    #
    # On a public command this is treated as anonymous rather than refused —
    # someone whose session expired should still see the public feed, and
    # they gain nothing by it. On a gated command it is a refusal.
    anonymous?(command) ? allow({}) : deny
  end
end