omniauth-proconnect 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 716624a37126c3740783be8dd3f69408107b5df3d0950de201ebdcab01ec1167
4
+ data.tar.gz: 82b492df655ad497fab377c09a5f03c86cfe7e501941463cc49d7722b2287e99
5
+ SHA512:
6
+ metadata.gz: ca9f4f94268c834a1b476f3a11d953d9cadd380df0d5412d3ce6c1ce5a72a1126edf3b3a133b00d6711373659d492eaef38a2f0a6a4f8d49f0a14f894a144ef2
7
+ data.tar.gz: 70b7118dcc78b31435c9d50a7af2cbba2ff8cc69eaf3b9dd2ed1cb82dc0480f490e4c1ffe5bbc895b91388056f2445559da082c830af53a6bfc06f039e1613f9
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,8 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.1
3
+
4
+ Style/StringLiterals:
5
+ EnforcedStyle: double_quotes
6
+
7
+ Style/StringLiteralsInInterpolation:
8
+ EnforcedStyle: double_quotes
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Stéphane Maniaci
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # Omniauth::Proconnect
2
+
3
+ Une stratégie pour [OmniAuth](https://github.com/omniauth/omniauth)
4
+ qui permet d'intégrer [ProConnect](https://www.proconnect.gouv.fr/).
5
+
6
+ ## Pourquoi pas `omniauth_openid_connect` ?
7
+
8
+ ProConnect comporte quelques particularités comme le retour des
9
+ informations utilisateurs (`/userinfo`) en JWT et l'obligation
10
+ d'intégrer le `id_token_hint` dans l'URL de fin de session quand la
11
+ spec officielle le décrit optionnel.
12
+
13
+ Ces spécificités empêchent pour le moment d'utiliser la librairie
14
+ générique
15
+ [`omniauth_openid_connect`](https://github.com/omniauth/omniauth_openid_connect)
16
+ qui malgré son degré de maturité supérieure semble à l'abandon aussi.
17
+
18
+ ## Utilisation
19
+
20
+ 1. installer la gem `bundle add omniauth-proconnect` ;
21
+ 2. configurer une nouvelle stratégie pour OmniAuth :
22
+
23
+ ```ruby
24
+ # config/omniauth.rb
25
+ Rails.application.config.middleware.use OmniAuth::Builder do
26
+ provider(
27
+ :pro_connect,
28
+ {
29
+ client_id: ENV.fetch("YOUR_APP_PC_CLIENT_ID"),
30
+ client_secret: ENV.fetch("YOUR_APP_PC_CLIENT_SECRET"),
31
+ proconnect_domain: ENV.fetch("YOUR_APP_PC_HOST"),
32
+ redirect_uri: ENV.fetch("YOUR_APP_PC_REDIRECT_URI"),
33
+ post_logout_redirect_uri: ENV.fetch("YOUR_APP_PC_POST_LOGOUT_REDIRECT_URI"),
34
+ scope: ENV.fetch("YOUR_APP_PC_SCOPES")
35
+ }
36
+ )
37
+ end
38
+ ```
39
+
40
+ 3. envoyez votre utilisateur sur la stratégie :
41
+
42
+ ```erb
43
+ <%= button_to "Se connecter via ProConnect", "/auth/proconnect", method: :post, remote: false, data: { turbo: false } %>
44
+ ```
45
+
46
+ 4. (optionnel) proposez la déconnexion aussi : le middleware observe
47
+ le chemin de la page courante et déclenchera le processus de fin de
48
+ session s'il se trouve sur `{request_path}/logout`, donc
49
+ `/auth/proconnect/logout` pour la majorité des cas :
50
+
51
+ ```ruby
52
+ redirect_to "/auth/proconnect/logout"
53
+ ```
54
+
55
+ ## Contribution
56
+
57
+ La stratégie est loin d'être complète ; n'hésitez pas à contribuer des
58
+ issues ou des changements.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Omniauth
4
+ module Proconnect
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "proconnect/version"
4
+
5
+ module Omniauth
6
+ class Proconnect
7
+ class Error < StandardError; end
8
+
9
+ include OmniAuth::Strategy
10
+
11
+ option :name, "proconnect"
12
+ option :client_id
13
+ option :client_secret
14
+ option :proconnect_domain
15
+ option :redirect_uri
16
+ option :post_logout_redirect_uri
17
+ option :scope, "openid email given_name usual_name"
18
+
19
+ def setup_phase
20
+ discover_endpoint!
21
+ end
22
+
23
+ def request_phase
24
+ redirect(authorization_uri)
25
+ end
26
+
27
+ def callback_phase
28
+ verify_state!(request.params["state"])
29
+
30
+ exchange_authorization_code!(request.params["code"])
31
+ .then { |response| store_tokens!(response) }
32
+ .then { |response| get_userinfo!(response) }
33
+ .then { |response| @userinfo = JSON::JWT.decode(response.body, :skip_verification) }
34
+ .then { super }
35
+ end
36
+
37
+ def other_phase
38
+ if on_logout_path?
39
+ engage_logout!
40
+ else
41
+ @app.call(env)
42
+ end
43
+ end
44
+
45
+ def uid
46
+ session["omniauth.pc.id_token"]["sub"]
47
+ end
48
+
49
+ def info
50
+ {
51
+ email: @userinfo["email"]
52
+ }
53
+ end
54
+
55
+ private
56
+
57
+ def connection
58
+ @connection ||= Faraday.new(url: options[:proconnect_domain]) do |c|
59
+ c.response :json
60
+ c.response :raise_error
61
+ end
62
+ end
63
+
64
+ def discovered_configuration
65
+ @discovered_configuration ||= discover_endpoint!
66
+ end
67
+
68
+ def discover_endpoint!
69
+ connection
70
+ .get(".well-known/openid-configuration")
71
+ .body
72
+ end
73
+
74
+ def authorization_uri
75
+ URI(discovered_configuration["authorization_endpoint"]).tap do |endpoint|
76
+ endpoint.query = URI.encode_www_form(
77
+ response_type: "code",
78
+ client_id: options[:client_id],
79
+ redirect_uri: options[:redirect_uri],
80
+ scope: options[:scope],
81
+ state: store_new_state!,
82
+ nonce: store_new_nonce!
83
+ )
84
+ end
85
+ end
86
+
87
+ def end_session_uri
88
+ URI(discovered_configuration["end_session_endpoint"]).tap do |endpoint|
89
+ endpoint.query = URI.encode_www_form(
90
+ id_token_hint: session["omniauth.pc.id_token"],
91
+ state: current_state,
92
+ post_logout_redirect_uri: options[:post_logout_redirect_uri]
93
+ )
94
+ end
95
+ end
96
+
97
+ def exchange_authorization_code!(code)
98
+ connection.post(URI(discovered_configuration["token_endpoint"]),
99
+ URI.encode_www_form(
100
+ grant_type: "authorization_code",
101
+ client_id: options[:client_id],
102
+ client_secret: options[:client_secret],
103
+ redirect_uri: options[:redirect_uri],
104
+ code: code,
105
+ scope: options[:scope]
106
+ ))
107
+ end
108
+
109
+ def store_tokens!(response)
110
+ response.tap do |res|
111
+ %w[access id refresh].each do |name|
112
+ session["omniauth.pc.#{name}_token"] = res.body["#{name}_token"]
113
+ end
114
+ end
115
+ end
116
+
117
+ def get_userinfo!
118
+ endpoint = URI(discovered_configuration["userinfo_endpoint"])
119
+ token = session["omniauth.pc.access_token"]
120
+
121
+ connection.get(endpoint, {}, "Authorization" => "Bearer #{token}")
122
+ end
123
+
124
+ def engage_logout!
125
+ redirect end_session_uri
126
+ end
127
+
128
+ def on_logout_path?
129
+ # FIXME: maybe don't hardcode this
130
+ request.path.end_with?("#{request_path}/logout")
131
+ end
132
+
133
+ def store_new_state!
134
+ session["omniauth.state"] = SecureRandom.hex(16)
135
+ end
136
+
137
+ def current_state
138
+ session["omniauth.state"]
139
+ end
140
+
141
+ def store_new_nonce!
142
+ session["omniauth.nonce"] = SecureRandom.hex(16)
143
+ end
144
+
145
+ def verify_state!(other_state)
146
+ if other_state != current_state
147
+ raise "a request came back with a different 'state' parameter than what we had last stored."
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,6 @@
1
+ module Omniauth
2
+ module Proconnect
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: omniauth-proconnect
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Stéphane Maniaci
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 2025-04-30 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: faraday
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2'
26
+ description: An OmniAuth strategy for ProConnect, an official OIDC solution for French
27
+ professionnals to login.
28
+ email:
29
+ - stephane.maniaci@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".rspec"
35
+ - ".rubocop.yml"
36
+ - LICENSE.txt
37
+ - README.md
38
+ - Rakefile
39
+ - lib/omniauth/proconnect.rb
40
+ - lib/omniauth/proconnect/version.rb
41
+ - sig/omniauth/proconnect.rbs
42
+ homepage: https://github.com/betagouv/omniauth-proconnect
43
+ licenses:
44
+ - MIT
45
+ metadata:
46
+ homepage_uri: https://github.com/betagouv/omniauth-proconnect
47
+ source_code_uri: https://github.com/betagouv/omniauth-proconnect
48
+ changelog_uri: https://github.com/betagouv/omniauth-proconnect
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 3.1.0
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubygems_version: 3.6.2
64
+ specification_version: 4
65
+ summary: An OmniAuth strategy for ProConnect
66
+ test_files: []