atlas-auth 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/atlas/rails/controller.rb +96 -0
- data/lib/atlas/rails/middleware.rb +56 -0
- data/lib/atlas/rails/railtie.rb +25 -0
- data/lib/atlas/rails.rb +146 -0
- data/lib/atlas/version.rb +1 -1
- metadata +34 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5d81044a8a8e996684c431cc963aa400821b8891b8eba14414b149230d5b7f9a
|
|
4
|
+
data.tar.gz: 437fbfbf228baacec51ce97db494bd080237d9d2bc96f31e895afa1ce0c78c7e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b233a0b594a57f68abcb5722d22ec4685fb2436c65c033e8dd083b1f28a4c222eb67579fa61b812a366200b2d5f8928bf017bafe00911e0885f2ed049af6ee03
|
|
7
|
+
data.tar.gz: aac3d1478f65863e359c4bbeeedafcaeabfc4c0afa0e5164f26d0b99a0e924789ece9b42a5ad28ae8a59ea751fe595ffeb9142f4631d1e6ac7bc9987a570ba6d
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/concern"
|
|
4
|
+
|
|
5
|
+
module Atlas
|
|
6
|
+
module Rails
|
|
7
|
+
# Controller mix-in exposing the request's Atlas session.
|
|
8
|
+
#
|
|
9
|
+
# The {Atlas::Rails::Middleware} has already verified the token by the time
|
|
10
|
+
# an action runs, so these helpers only read +env["atlas.auth"]+ — they
|
|
11
|
+
# never touch crypto.
|
|
12
|
+
#
|
|
13
|
+
# class ApplicationController < ActionController::Base
|
|
14
|
+
# include Atlas::Rails::Controller
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# class BillingController < ApplicationController
|
|
18
|
+
# before_action :require_atlas_auth!
|
|
19
|
+
# before_action -> { atlas_authorize!(permission: "org:billing:manage") }
|
|
20
|
+
#
|
|
21
|
+
# def show
|
|
22
|
+
# render json: { user: current_atlas_user_id }
|
|
23
|
+
# end
|
|
24
|
+
# end
|
|
25
|
+
module Controller
|
|
26
|
+
extend ActiveSupport::Concern
|
|
27
|
+
|
|
28
|
+
# The verified {Atlas::VerifyResult} for this request (ok or failed), or a
|
|
29
|
+
# failure result when the middleware is not in the stack.
|
|
30
|
+
# @return [Atlas::VerifyResult]
|
|
31
|
+
def current_atlas_session
|
|
32
|
+
request.env[Atlas::Rails::Middleware::ENV_KEY] ||
|
|
33
|
+
Atlas::VerifyResult.failure(:invalid)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The verified claims hash (string-keyed), or nil when unauthenticated.
|
|
37
|
+
# @return [Hash, nil]
|
|
38
|
+
def current_atlas_claims
|
|
39
|
+
session = current_atlas_session
|
|
40
|
+
session.ok? ? session.claims : nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# The authenticated user id (+sub+), or nil.
|
|
44
|
+
# @return [String, nil]
|
|
45
|
+
def current_atlas_user_id
|
|
46
|
+
current_atlas_claims&.fetch("sub", nil)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# True when this request carries a valid Atlas session.
|
|
50
|
+
def atlas_authenticated?
|
|
51
|
+
current_atlas_session.ok?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# +before_action+ guard: render 401 and halt unless authenticated.
|
|
55
|
+
def require_atlas_auth!
|
|
56
|
+
return true if atlas_authenticated?
|
|
57
|
+
|
|
58
|
+
render_atlas_unauthorized
|
|
59
|
+
false
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# +before_action+ guard for a specific authorization condition, e.g.
|
|
63
|
+
# +atlas_authorize!(permission: "org:billing:manage")+. Renders 401 when
|
|
64
|
+
# unauthenticated and 403 when authenticated but not permitted. Accepts the
|
|
65
|
+
# same conditions as {Atlas::VerifyResult#has?}
|
|
66
|
+
# (+:permission+, +:role+, +:any_permission+, +:all_permissions+).
|
|
67
|
+
def atlas_authorize!(**condition)
|
|
68
|
+
unless atlas_authenticated?
|
|
69
|
+
render_atlas_unauthorized
|
|
70
|
+
return false
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
unless current_atlas_session.has?(condition)
|
|
74
|
+
render_atlas_forbidden
|
|
75
|
+
return false
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
true
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def render_atlas_unauthorized
|
|
84
|
+
render json: {
|
|
85
|
+
errors: [{ "code" => "UNAUTHENTICATED", "message" => "Authentication required." }],
|
|
86
|
+
}, status: :unauthorized
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def render_atlas_forbidden
|
|
90
|
+
render json: {
|
|
91
|
+
errors: [{ "code" => "FORBIDDEN", "message" => "You do not have permission to do that." }],
|
|
92
|
+
}, status: :forbidden
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Atlas
|
|
4
|
+
module Rails
|
|
5
|
+
# Rack middleware that authenticates every request and never blocks one.
|
|
6
|
+
#
|
|
7
|
+
# It reads the session JWT from the +Authorization: Bearer+ header (which
|
|
8
|
+
# wins) or the +__session+ cookie, verifies it through the gem's
|
|
9
|
+
# {Atlas::Backend}, and leaves the {Atlas::VerifyResult} at
|
|
10
|
+
# +env["atlas.auth"]+. Whether the token is present, absent or bad, the
|
|
11
|
+
# request passes through — enforcement is the controller's job
|
|
12
|
+
# ({Atlas::Rails::Controller#require_atlas_auth!}), so a public action and a
|
|
13
|
+
# protected one can share the same stack.
|
|
14
|
+
class Middleware
|
|
15
|
+
# The Rack env key the verified result is stashed under.
|
|
16
|
+
ENV_KEY = "atlas.auth"
|
|
17
|
+
|
|
18
|
+
# @param app [#call] the next Rack app.
|
|
19
|
+
# @param backend [Atlas::Backend, nil] an explicit verifier; when nil the
|
|
20
|
+
# shared {Atlas.configuration} backend is used (and built lazily).
|
|
21
|
+
# @param configuration [Atlas::Configuration, nil] an explicit config.
|
|
22
|
+
def initialize(app, backend: nil, configuration: nil)
|
|
23
|
+
@app = app
|
|
24
|
+
@backend = backend
|
|
25
|
+
@configuration = configuration
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def call(env)
|
|
29
|
+
env[ENV_KEY] = authenticate(env)
|
|
30
|
+
@app.call(env)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
# Always returns a {Atlas::VerifyResult}, never raises. A missing or
|
|
36
|
+
# unconfigured backend degrades to "unauthenticated", never a 500.
|
|
37
|
+
def authenticate(env)
|
|
38
|
+
verifier = backend
|
|
39
|
+
return Atlas::VerifyResult.failure(:invalid) if verifier.nil?
|
|
40
|
+
|
|
41
|
+
verifier.authenticate_request(
|
|
42
|
+
"authorization" => env["HTTP_AUTHORIZATION"],
|
|
43
|
+
"cookie" => env["HTTP_COOKIE"],
|
|
44
|
+
)
|
|
45
|
+
rescue StandardError
|
|
46
|
+
Atlas::VerifyResult.failure(:invalid)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def backend
|
|
50
|
+
@backend ||= (@configuration || Atlas.configuration).backend
|
|
51
|
+
rescue Atlas::ConfigurationError
|
|
52
|
+
nil
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Defined only inside a Rails app. Requiring "atlas/rails" from a plain Ruby
|
|
4
|
+
# process (no Rails loaded) leaves this a no-op — the Railtie never comes into
|
|
5
|
+
# being, so nothing tries to touch a middleware stack that isn't there.
|
|
6
|
+
if defined?(::Rails::Railtie)
|
|
7
|
+
module Atlas
|
|
8
|
+
module Rails
|
|
9
|
+
# Wires the integration into the host Rails app with no manual steps:
|
|
10
|
+
# inserts the auth middleware and mixes the controller helpers into every
|
|
11
|
+
# controller once ActionController loads.
|
|
12
|
+
class Railtie < ::Rails::Railtie
|
|
13
|
+
initializer "atlas.middleware" do |app|
|
|
14
|
+
app.middleware.use Atlas::Rails::Middleware
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
initializer "atlas.controller" do
|
|
18
|
+
ActiveSupport.on_load(:action_controller) do
|
|
19
|
+
include Atlas::Rails::Controller
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/atlas/rails.rb
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../atlas"
|
|
4
|
+
|
|
5
|
+
# atlas-auth — Rails-native integration.
|
|
6
|
+
#
|
|
7
|
+
# This file is loaded ONLY when you ask for it:
|
|
8
|
+
#
|
|
9
|
+
# # Gemfile
|
|
10
|
+
# gem "atlas-auth", require: "atlas/rails"
|
|
11
|
+
#
|
|
12
|
+
# Requiring the base gem (+require "atlas"+) never pulls Rails in, so the SDK
|
|
13
|
+
# stays usable in a plain Ruby or non-Rails service. Loading this file in a
|
|
14
|
+
# Rails app registers {Atlas::Rails::Railtie}, which:
|
|
15
|
+
#
|
|
16
|
+
# * inserts {Atlas::Rails::Middleware} — it verifies the session JWT carried by
|
|
17
|
+
# the +Authorization: Bearer+ header (or the +__session+ cookie) and stashes
|
|
18
|
+
# the {Atlas::VerifyResult} in +env["atlas.auth"]+, and
|
|
19
|
+
# * mixes {Atlas::Rails::Controller} into every controller, giving you
|
|
20
|
+
# +current_atlas_session+, +current_atlas_user_id+, +atlas_authenticated?+,
|
|
21
|
+
# +require_atlas_auth!+ and +atlas_authorize!+.
|
|
22
|
+
#
|
|
23
|
+
# All verification is delegated to the gem's existing {Atlas::Backend} — this
|
|
24
|
+
# layer adds no crypto of its own. Configure it once:
|
|
25
|
+
#
|
|
26
|
+
# Atlas.configure do |c|
|
|
27
|
+
# c.jwks_url = "https://your-instance.atlasauth.net/v1/jwks"
|
|
28
|
+
# c.issuer = "https://your-instance.atlasauth.net"
|
|
29
|
+
# c.secret_key = Rails.application.credentials.dig(:atlas, :secret_key)
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# or leave it unset and let it read +ATLAS_JWKS_URL+ / +ATLAS_ISSUER+ /
|
|
33
|
+
# +ATLAS_SECRET_KEY+ from the environment or Rails credentials (+atlas:+).
|
|
34
|
+
module Atlas
|
|
35
|
+
# Process-wide configuration for the Rails integration. A single
|
|
36
|
+
# {Atlas::Backend} is built from it, lazily, and reused across requests
|
|
37
|
+
# (its JWKS cache is the whole point of keeping one instance).
|
|
38
|
+
class Configuration
|
|
39
|
+
# @return [String, nil] the instance JWKS URL (+ATLAS_JWKS_URL+).
|
|
40
|
+
attr_accessor :jwks_url
|
|
41
|
+
# @return [String, nil] expected +iss+ (+ATLAS_ISSUER+).
|
|
42
|
+
attr_accessor :issuer
|
|
43
|
+
# @return [String, nil] +sk_+ key, only needed for +verify_online+ (+ATLAS_SECRET_KEY+).
|
|
44
|
+
attr_accessor :secret_key
|
|
45
|
+
# @return [String, nil] BAPI base URL, only needed for +verify_online+.
|
|
46
|
+
attr_accessor :bapi_base_url
|
|
47
|
+
# @return [Array<String>, nil] optional +azp+ allowlist.
|
|
48
|
+
attr_accessor :authorized_parties
|
|
49
|
+
|
|
50
|
+
def initialize
|
|
51
|
+
@jwks_url = resolve("ATLAS_JWKS_URL", :jwks_url)
|
|
52
|
+
@issuer = resolve("ATLAS_ISSUER", :issuer)
|
|
53
|
+
@secret_key = resolve("ATLAS_SECRET_KEY", :secret_key)
|
|
54
|
+
@bapi_base_url = resolve("ATLAS_BAPI_BASE_URL", :bapi_base_url)
|
|
55
|
+
@authorized_parties = nil
|
|
56
|
+
@backend = nil
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# True once the minimum needed for local verification is present.
|
|
60
|
+
def configured?
|
|
61
|
+
!blank?(jwks_url) && !blank?(issuer)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# The shared, memoized verifier. Raises {Atlas::ConfigurationError} rather
|
|
65
|
+
# than silently building a backend that can never verify anything.
|
|
66
|
+
# @return [Atlas::Backend]
|
|
67
|
+
def backend
|
|
68
|
+
unless configured?
|
|
69
|
+
raise Atlas::ConfigurationError,
|
|
70
|
+
"Atlas Rails integration needs jwks_url and issuer. Set them via " \
|
|
71
|
+
"Atlas.configure, or ATLAS_JWKS_URL / ATLAS_ISSUER (env or Rails " \
|
|
72
|
+
"credentials under `atlas:`)."
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
@backend ||= Atlas::Backend.new(
|
|
76
|
+
jwks_url: jwks_url,
|
|
77
|
+
issuer: issuer,
|
|
78
|
+
authorized_parties: authorized_parties,
|
|
79
|
+
secret_key: secret_key,
|
|
80
|
+
bapi_base_url: bapi_base_url,
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Drop the memoized backend so the next request rebuilds it. Called after
|
|
85
|
+
# {Atlas.configure} so edited values (and a fresh JWKS cache) take effect.
|
|
86
|
+
def reset_backend!
|
|
87
|
+
@backend = nil
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def blank?(value)
|
|
93
|
+
value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# ENV first, then Rails credentials under the +atlas:+ namespace.
|
|
97
|
+
def resolve(env_key, cred_key)
|
|
98
|
+
value = ENV[env_key]
|
|
99
|
+
return value unless blank?(value)
|
|
100
|
+
|
|
101
|
+
rails_credential(cred_key)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def rails_credential(key)
|
|
105
|
+
return nil unless defined?(::Rails) && ::Rails.respond_to?(:application) && ::Rails.application
|
|
106
|
+
|
|
107
|
+
atlas = ::Rails.application.credentials.respond_to?(:atlas) ? ::Rails.application.credentials.atlas : nil
|
|
108
|
+
return nil if atlas.nil?
|
|
109
|
+
|
|
110
|
+
atlas.respond_to?(:[]) ? (atlas[key] || atlas[key.to_s]) : nil
|
|
111
|
+
rescue StandardError
|
|
112
|
+
nil
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
class << self
|
|
117
|
+
# The current {Atlas::Configuration}, created on first use.
|
|
118
|
+
def configuration
|
|
119
|
+
@configuration ||= Configuration.new
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Configure the Rails integration.
|
|
123
|
+
#
|
|
124
|
+
# Atlas.configure do |c|
|
|
125
|
+
# c.jwks_url = "https://your-instance.atlasauth.net/v1/jwks"
|
|
126
|
+
# c.issuer = "https://your-instance.atlasauth.net"
|
|
127
|
+
# end
|
|
128
|
+
#
|
|
129
|
+
# @yieldparam config [Atlas::Configuration]
|
|
130
|
+
# @return [Atlas::Configuration]
|
|
131
|
+
def configure
|
|
132
|
+
yield(configuration) if block_given?
|
|
133
|
+
configuration.reset_backend!
|
|
134
|
+
configuration
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Forget all configuration. Primarily for tests.
|
|
138
|
+
def reset_configuration!
|
|
139
|
+
@configuration = nil
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
require_relative "rails/middleware"
|
|
145
|
+
require_relative "rails/controller"
|
|
146
|
+
require_relative "rails/railtie"
|
data/lib/atlas/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: atlas-auth
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Atlas
|
|
@@ -51,6 +51,34 @@ dependencies:
|
|
|
51
51
|
- - "~>"
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: '13.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: rails
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '6.1'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '6.1'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: rack-test
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '1.1'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '1.1'
|
|
54
82
|
description: |
|
|
55
83
|
A typed Ruby client over the Atlas Backend API (BAPI), the Ruby peer of the
|
|
56
84
|
TypeScript @atlasauth/backend and Python atlas-backend SDKs. Covers the full
|
|
@@ -72,6 +100,10 @@ files:
|
|
|
72
100
|
- lib/atlas/handshake.rb
|
|
73
101
|
- lib/atlas/jwks_cache.rb
|
|
74
102
|
- lib/atlas/pagination.rb
|
|
103
|
+
- lib/atlas/rails.rb
|
|
104
|
+
- lib/atlas/rails/controller.rb
|
|
105
|
+
- lib/atlas/rails/middleware.rb
|
|
106
|
+
- lib/atlas/rails/railtie.rb
|
|
75
107
|
- lib/atlas/resources/actions.rb
|
|
76
108
|
- lib/atlas/resources/actor_tokens.rb
|
|
77
109
|
- lib/atlas/resources/api_keys.rb
|
|
@@ -124,7 +156,7 @@ licenses:
|
|
|
124
156
|
- MIT
|
|
125
157
|
metadata:
|
|
126
158
|
homepage_uri: https://atlasauth.net
|
|
127
|
-
source_code_uri: https://github.com/
|
|
159
|
+
source_code_uri: https://github.com/alphajew420/ssoly/tree/main/sdks/ruby
|
|
128
160
|
documentation_uri: https://atlasauth.net/docs/sdks/ruby
|
|
129
161
|
rubygems_mfa_required: 'true'
|
|
130
162
|
rdoc_options: []
|